mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-07 03:48:58 +08:00
fix webui skill
This commit is contained in:
@@ -795,7 +795,9 @@ func (s *RegistryServer) handleWebUISkills(w http.ResponseWriter, r *http.Reques
|
|||||||
seenDirs := map[string]struct{}{}
|
seenDirs := map[string]struct{}{}
|
||||||
seenSkills := map[string]struct{}{}
|
seenSkills := map[string]struct{}{}
|
||||||
items := make([]skillItem, 0)
|
items := make([]skillItem, 0)
|
||||||
checkUpdates := strings.TrimSpace(r.URL.Query().Get("check_updates")) != "0"
|
// Default off to avoid hammering clawhub search API on each UI refresh.
|
||||||
|
// Enable explicitly with ?check_updates=1 when needed.
|
||||||
|
checkUpdates := strings.TrimSpace(r.URL.Query().Get("check_updates")) == "1"
|
||||||
|
|
||||||
for _, dir := range candDirs {
|
for _, dir := range candDirs {
|
||||||
dir = strings.TrimSpace(dir)
|
dir = strings.TrimSpace(dir)
|
||||||
@@ -918,7 +920,13 @@ func (s *RegistryServer) handleWebUISkills(w http.ResponseWriter, r *http.Reques
|
|||||||
cmd.Dir = strings.TrimSpace(s.workspacePath)
|
cmd.Dir = strings.TrimSpace(s.workspacePath)
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("install failed: %v\n%s", err, string(out)), http.StatusInternalServerError)
|
outText := string(out)
|
||||||
|
lower := strings.ToLower(outText)
|
||||||
|
if strings.Contains(lower, "rate limit exceeded") || strings.Contains(lower, "too many requests") {
|
||||||
|
http.Error(w, fmt.Sprintf("clawhub rate limit exceeded. please retry later or configure auth token.\n%s", outText), http.StatusTooManyRequests)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, fmt.Sprintf("install failed: %v\n%s", err, outText), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{"ok": true, "installed": name, "output": string(out)})
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{"ok": true, "installed": name, "output": string(out)})
|
||||||
|
|||||||
@@ -175,6 +175,9 @@ const resources = {
|
|||||||
skillsInstallFailedMessage: 'Failed to install clawhub',
|
skillsInstallFailedMessage: 'Failed to install clawhub',
|
||||||
skillsInstallDoneTitle: 'Install complete',
|
skillsInstallDoneTitle: 'Install complete',
|
||||||
skillsInstallDoneMessage: 'clawhub is installed. You can continue installing skills.',
|
skillsInstallDoneMessage: 'clawhub is installed. You can continue installing skills.',
|
||||||
|
skillsInstallingSkill: 'Installing skill...',
|
||||||
|
skillsInstallSkillDoneTitle: 'Skill installed',
|
||||||
|
skillsInstallSkillDoneMessage: 'Skill "{{name}}" installed successfully.',
|
||||||
skillsAddTitle: 'Add Skill',
|
skillsAddTitle: 'Add Skill',
|
||||||
skillsAddMessage: 'Upload skill archive (.zip / .tar.gz / .tgz / .tar). It will be extracted into skills folder and archive will be removed.',
|
skillsAddMessage: 'Upload skill archive (.zip / .tar.gz / .tgz / .tar). It will be extracted into skills folder and archive will be removed.',
|
||||||
skillsSelectArchive: 'Select archive',
|
skillsSelectArchive: 'Select archive',
|
||||||
@@ -474,6 +477,9 @@ const resources = {
|
|||||||
skillsInstallFailedMessage: '安装 clawhub 失败',
|
skillsInstallFailedMessage: '安装 clawhub 失败',
|
||||||
skillsInstallDoneTitle: '安装完成',
|
skillsInstallDoneTitle: '安装完成',
|
||||||
skillsInstallDoneMessage: 'clawhub 已安装,可继续安装技能。',
|
skillsInstallDoneMessage: 'clawhub 已安装,可继续安装技能。',
|
||||||
|
skillsInstallingSkill: '正在安装技能...',
|
||||||
|
skillsInstallSkillDoneTitle: '技能安装完成',
|
||||||
|
skillsInstallSkillDoneMessage: '技能 "{{name}}" 安装成功。',
|
||||||
skillsAddTitle: '添加技能',
|
skillsAddTitle: '添加技能',
|
||||||
skillsAddMessage: '请上传技能压缩包(.zip / .tar.gz / .tgz / .tar)。上传后将自动解压到 skills 目录,并删除上传压缩包。',
|
skillsAddMessage: '请上传技能压缩包(.zip / .tar.gz / .tgz / .tar)。上传后将自动解压到 skills 目录,并删除上传压缩包。',
|
||||||
skillsSelectArchive: '选择压缩包',
|
skillsSelectArchive: '选择压缩包',
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const Skills: React.FC = () => {
|
|||||||
const { skills, refreshSkills, q, clawhubInstalled, clawhubPath } = useAppContext();
|
const { skills, refreshSkills, q, clawhubInstalled, clawhubPath } = useAppContext();
|
||||||
const ui = useUI();
|
const ui = useUI();
|
||||||
const [installName, setInstallName] = useState('');
|
const [installName, setInstallName] = useState('');
|
||||||
|
const [installingSkill, setInstallingSkill] = useState(false);
|
||||||
const qp = (k: string, v: string) => `${q}${q ? '&' : '?'}${k}=${encodeURIComponent(v)}`;
|
const qp = (k: string, v: string) => `${q}${q ? '&' : '?'}${k}=${encodeURIComponent(v)}`;
|
||||||
|
|
||||||
const [isFileModalOpen, setIsFileModalOpen] = useState(false);
|
const [isFileModalOpen, setIsFileModalOpen] = useState(false);
|
||||||
@@ -47,35 +48,49 @@ const Skills: React.FC = () => {
|
|||||||
});
|
});
|
||||||
const text = await r.text();
|
const text = await r.text();
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
|
ui.hideLoading();
|
||||||
await ui.notify({ title: t('skillsInstallFailedTitle'), message: text || t('skillsInstallFailedMessage') });
|
await ui.notify({ title: t('skillsInstallFailedTitle'), message: text || t('skillsInstallFailedMessage') });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
ui.hideLoading();
|
||||||
await ui.notify({ title: t('skillsInstallDoneTitle'), message: t('skillsInstallDoneMessage') });
|
await ui.notify({ title: t('skillsInstallDoneTitle'), message: t('skillsInstallDoneMessage') });
|
||||||
await refreshSkills();
|
await refreshSkills();
|
||||||
return true;
|
return true;
|
||||||
} finally {
|
} finally {
|
||||||
|
// loading is explicitly closed before notify, keep this as fallback.
|
||||||
ui.hideLoading();
|
ui.hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function installSkill() {
|
async function installSkill() {
|
||||||
|
if (installingSkill) return;
|
||||||
const name = installName.trim();
|
const name = installName.trim();
|
||||||
if (!name) return;
|
if (!name) return;
|
||||||
|
|
||||||
const ready = await installClawHubIfNeeded();
|
setInstallingSkill(true);
|
||||||
if (!ready) return;
|
ui.showLoading(t('skillsInstallingSkill'));
|
||||||
|
try {
|
||||||
|
const ready = await installClawHubIfNeeded();
|
||||||
|
if (!ready) return;
|
||||||
|
|
||||||
const r = await fetch(`/webui/api/skills${q}`, {
|
const r = await fetch(`/webui/api/skills${q}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ action: 'install', name }),
|
body: JSON.stringify({ action: 'install', name }),
|
||||||
});
|
});
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
await ui.notify({ title: t('requestFailed'), message: await r.text() });
|
ui.hideLoading();
|
||||||
return;
|
await ui.notify({ title: t('requestFailed'), message: await r.text() });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setInstallName('');
|
||||||
|
await refreshSkills();
|
||||||
|
ui.hideLoading();
|
||||||
|
await ui.notify({ title: t('skillsInstallSkillDoneTitle'), message: t('skillsInstallSkillDoneMessage', { name }) });
|
||||||
|
} finally {
|
||||||
|
ui.hideLoading();
|
||||||
|
setInstallingSkill(false);
|
||||||
}
|
}
|
||||||
setInstallName('');
|
|
||||||
await refreshSkills();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onAddSkillClick() {
|
async function onAddSkillClick() {
|
||||||
@@ -173,8 +188,8 @@ const Skills: React.FC = () => {
|
|||||||
<div className="flex items-center justify-between gap-3 flex-wrap">
|
<div className="flex items-center justify-between gap-3 flex-wrap">
|
||||||
<h1 className="text-2xl font-semibold tracking-tight">{t('skills')}</h1>
|
<h1 className="text-2xl font-semibold tracking-tight">{t('skills')}</h1>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<input value={installName} onChange={(e) => setInstallName(e.target.value)} placeholder={t('skillsNamePlaceholder')} className="px-3 py-2 bg-zinc-950 border border-zinc-800 rounded-lg text-sm" />
|
<input disabled={installingSkill} value={installName} onChange={(e) => setInstallName(e.target.value)} placeholder={t('skillsNamePlaceholder')} className="px-3 py-2 bg-zinc-950 border border-zinc-800 rounded-lg text-sm disabled:opacity-60" />
|
||||||
<button onClick={installSkill} className="px-3 py-2 bg-emerald-600 hover:bg-emerald-500 text-white rounded-lg text-sm font-medium">{t('install')}</button>
|
<button disabled={installingSkill} onClick={installSkill} className="px-3 py-2 bg-emerald-600 hover:bg-emerald-500 disabled:opacity-60 disabled:cursor-not-allowed text-white rounded-lg text-sm font-medium">{installingSkill ? t('loading') : t('install')}</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className={`text-xs px-2 py-1 rounded-md border ${clawhubInstalled ? 'text-emerald-300 border-emerald-700/50 bg-emerald-900/20' : 'text-amber-300 border-amber-700/50 bg-amber-900/20'}`} title={clawhubPath || t('skillsClawhubNotFound')}>
|
<div className={`text-xs px-2 py-1 rounded-md border ${clawhubInstalled ? 'text-emerald-300 border-emerald-700/50 bg-emerald-900/20' : 'text-amber-300 border-amber-700/50 bg-amber-900/20'}`} title={clawhubPath || t('skillsClawhubNotFound')}>
|
||||||
|
|||||||
Reference in New Issue
Block a user