mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-23 00:00:52 +00:00
feat: The skill marketplace supports ClawHub China acceleration. (#12396)
This commit is contained in:
@@ -544,7 +544,7 @@ type AgentConfigFile struct {
|
||||
|
||||
type AgentSkillSearchReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Source string `json:"source" validate:"required,oneof=clawhub skillhub"`
|
||||
Source string `json:"source" validate:"required,oneof=clawhub-global clawhub-cn skillhub"`
|
||||
Keyword string `json:"keyword" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -574,7 +574,7 @@ type AgentSkillUpdateReq struct {
|
||||
|
||||
type AgentSkillInstallReq struct {
|
||||
AgentID uint `json:"agentId" validate:"required"`
|
||||
Source string `json:"source" validate:"required,oneof=clawhub skillhub"`
|
||||
Source string `json:"source" validate:"required,oneof=clawhub-global clawhub-cn skillhub"`
|
||||
Slug string `json:"slug" validate:"required"`
|
||||
TaskID string `json:"taskID" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
||||
)
|
||||
|
||||
const clawhubGlobalRegistry = "https://clawhub.com"
|
||||
const clawhubChinaRegistry = "https://mirror-cn.clawhub.com"
|
||||
|
||||
type openclawSkillsList struct {
|
||||
Skills []openclawSkillListItem `json:"skills"`
|
||||
}
|
||||
@@ -74,7 +77,7 @@ func (a AgentService) SearchSkills(req dto.AgentSkillSearchReq) ([]dto.AgentSkil
|
||||
case "skillhub":
|
||||
return parseSkillhubSearchResult(output)
|
||||
default:
|
||||
return parseClawhubSearchResult(output), nil
|
||||
return parseClawhubSearchResult(output, req.Source), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +155,13 @@ func loadOpenclawSkillSearchOutput(containerName, source, keyword string) (strin
|
||||
case "skillhub":
|
||||
return runDockerExecWithStdout(30*time.Second, containerName, "skillhub", "search", keyword, "--json")
|
||||
default:
|
||||
return runDockerExecWithStdout(30*time.Second, containerName, "clawhub", "search", keyword)
|
||||
return runDockerExecWithStdout(
|
||||
30*time.Second,
|
||||
containerName,
|
||||
"sh",
|
||||
"-c",
|
||||
fmt.Sprintf("CLAWHUB_REGISTRY=%q clawhub search %q", resolveClawhubRegistry(source), keyword),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +191,7 @@ func parseSkillhubSearchResult(output string) ([]dto.AgentSkillSearchItem, error
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func parseClawhubSearchResult(output string) []dto.AgentSkillSearchItem {
|
||||
func parseClawhubSearchResult(output, source string) []dto.AgentSkillSearchItem {
|
||||
lines := strings.Split(strings.TrimSpace(output), "\n")
|
||||
items := make([]dto.AgentSkillSearchItem, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
@@ -194,7 +203,7 @@ func parseClawhubSearchResult(output string) []dto.AgentSkillSearchItem {
|
||||
Slug: matches[1],
|
||||
Name: matches[2],
|
||||
Score: matches[3],
|
||||
Source: "clawhub",
|
||||
Source: source,
|
||||
})
|
||||
}
|
||||
return items
|
||||
@@ -202,10 +211,11 @@ func parseClawhubSearchResult(output string) []dto.AgentSkillSearchItem {
|
||||
|
||||
func buildOpenclawSkillInstallCommand(source, slug string) string {
|
||||
switch source {
|
||||
case "clawhub":
|
||||
case "clawhub-global", "clawhub-cn":
|
||||
return fmt.Sprintf(
|
||||
"mkdir -p %s && clawhub --workdir /home/node/.openclaw --dir skills install %q",
|
||||
"mkdir -p %s && CLAWHUB_REGISTRY=%q clawhub --workdir /home/node/.openclaw --dir skills install %q",
|
||||
openclawManagedSkillsDir,
|
||||
resolveClawhubRegistry(source),
|
||||
slug,
|
||||
)
|
||||
default:
|
||||
@@ -218,6 +228,15 @@ func buildOpenclawSkillInstallCommand(source, slug string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func resolveClawhubRegistry(source string) string {
|
||||
switch source {
|
||||
case "clawhub-cn":
|
||||
return clawhubChinaRegistry
|
||||
default:
|
||||
return clawhubGlobalRegistry
|
||||
}
|
||||
}
|
||||
|
||||
func getOpenclawSkillKey(containerName, name string) (string, error) {
|
||||
output, err := runDockerExecWithStdout(30*time.Second, containerName, "sh", "-c", fmt.Sprintf("openclaw skills info %q --json 2>&1", name))
|
||||
if err != nil {
|
||||
|
||||
@@ -801,7 +801,7 @@ export namespace AI {
|
||||
|
||||
export interface AgentSkillSearchReq {
|
||||
agentId: number;
|
||||
source: 'clawhub' | 'skillhub';
|
||||
source: 'clawhub-global' | 'clawhub-cn' | 'skillhub';
|
||||
keyword: string;
|
||||
}
|
||||
|
||||
@@ -831,7 +831,7 @@ export namespace AI {
|
||||
|
||||
export interface AgentSkillInstallReq {
|
||||
agentId: number;
|
||||
source: 'clawhub' | 'skillhub';
|
||||
source: 'clawhub-global' | 'clawhub-cn' | 'skillhub';
|
||||
slug: string;
|
||||
taskID: string;
|
||||
}
|
||||
|
||||
@@ -712,7 +712,8 @@ const message = {
|
||||
skillsMarket: 'Skill Market',
|
||||
skillsMarketHint: 'Select a source and search for skills',
|
||||
skillsMarketEmpty: 'No matching skills found',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Official)',
|
||||
skillsMarketSourceClawhubGlobal: 'ClawHub (Global)',
|
||||
skillsMarketSourceClawhubChina: 'ClawHub (China)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
|
||||
skillsScore: 'Score',
|
||||
versionUnsupportedTitle: 'This feature is not supported in the current version',
|
||||
|
||||
@@ -721,7 +721,8 @@ const message = {
|
||||
skillsMarket: 'Mercado de habilidades',
|
||||
skillsMarketHint: 'Seleccione una fuente y busque habilidades',
|
||||
skillsMarketEmpty: 'No se encontraron habilidades relacionadas',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Oficial)',
|
||||
skillsMarketSourceClawhubGlobal: 'ClawHub (Global)',
|
||||
skillsMarketSourceClawhubChina: 'ClawHub (China)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
|
||||
skillsScore: 'Puntuación',
|
||||
versionUnsupportedTitle: 'Esta función no es compatible con la versión actual',
|
||||
|
||||
@@ -713,7 +713,8 @@ const message = {
|
||||
skillsMarket: '技能マーケット',
|
||||
skillsMarketHint: 'ソースを選択して技能を検索してください',
|
||||
skillsMarketEmpty: '関連する技能が見つかりませんでした',
|
||||
skillsMarketSourceClawhub: 'ClawHub(公式)',
|
||||
skillsMarketSourceClawhubGlobal: 'ClawHub(グローバル)',
|
||||
skillsMarketSourceClawhubChina: 'ClawHub(中国)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub(Tencent)',
|
||||
skillsScore: 'スコア',
|
||||
versionUnsupportedTitle: '現在のバージョンではこの機能はサポートされていません',
|
||||
|
||||
@@ -705,7 +705,8 @@ const message = {
|
||||
skillsMarket: '기술 마켓',
|
||||
skillsMarketHint: '소스를 선택하고 기술을 검색하세요',
|
||||
skillsMarketEmpty: '관련 기술을 찾을 수 없습니다',
|
||||
skillsMarketSourceClawhub: 'ClawHub (공식)',
|
||||
skillsMarketSourceClawhubGlobal: 'ClawHub (글로벌)',
|
||||
skillsMarketSourceClawhubChina: 'ClawHub (중국)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
|
||||
skillsScore: '점수',
|
||||
versionUnsupportedTitle: '현재 버전에서는 이 기능을 지원하지 않습니다',
|
||||
|
||||
@@ -720,7 +720,8 @@ const message = {
|
||||
skillsMarket: 'Pasaran kemahiran',
|
||||
skillsMarketHint: 'Pilih sumber dan cari kemahiran',
|
||||
skillsMarketEmpty: 'Tiada kemahiran berkaitan ditemui',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Rasmi)',
|
||||
skillsMarketSourceClawhubGlobal: 'ClawHub (Global)',
|
||||
skillsMarketSourceClawhubChina: 'ClawHub (China)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
|
||||
skillsScore: 'Skor',
|
||||
versionUnsupportedTitle: 'Ciri ini tidak disokong dalam versi semasa',
|
||||
|
||||
@@ -715,7 +715,8 @@ const message = {
|
||||
skillsMarket: 'Mercado de habilidades',
|
||||
skillsMarketHint: 'Selecione uma fonte e pesquise habilidades',
|
||||
skillsMarketEmpty: 'Nenhuma habilidade correspondente encontrada',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Oficial)',
|
||||
skillsMarketSourceClawhubGlobal: 'ClawHub (Global)',
|
||||
skillsMarketSourceClawhubChina: 'ClawHub (China)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
|
||||
skillsScore: 'Pontuação',
|
||||
versionUnsupportedTitle: 'Este recurso não é compatível com a versão atual',
|
||||
|
||||
@@ -712,7 +712,8 @@ const message = {
|
||||
skillsMarket: 'Рынок навыков',
|
||||
skillsMarketHint: 'Выберите источник и выполните поиск навыков',
|
||||
skillsMarketEmpty: 'Подходящие навыки не найдены',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Официальный)',
|
||||
skillsMarketSourceClawhubGlobal: 'ClawHub (Глобальный)',
|
||||
skillsMarketSourceClawhubChina: 'ClawHub (Китай)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
|
||||
skillsScore: 'Оценка',
|
||||
versionUnsupportedTitle: 'Эта функция не поддерживается в текущей версии',
|
||||
|
||||
@@ -716,7 +716,8 @@ const message = {
|
||||
skillsMarket: 'Yetenek pazarı',
|
||||
skillsMarketHint: 'Bir kaynak seçin ve yetenek arayın',
|
||||
skillsMarketEmpty: 'Eşleşen yetenek bulunamadı',
|
||||
skillsMarketSourceClawhub: 'ClawHub (Resmi)',
|
||||
skillsMarketSourceClawhubGlobal: 'ClawHub (Global)',
|
||||
skillsMarketSourceClawhubChina: 'ClawHub (Çin)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub (Tencent)',
|
||||
skillsScore: 'Puan',
|
||||
versionUnsupportedTitle: 'Bu özellik mevcut sürümde desteklenmiyor',
|
||||
|
||||
@@ -678,7 +678,8 @@ const message = {
|
||||
skillsMarket: '技能市場',
|
||||
skillsMarketHint: '請選擇來源並搜尋技能',
|
||||
skillsMarketEmpty: '未找到相關技能',
|
||||
skillsMarketSourceClawhub: 'ClawHub(官方)',
|
||||
skillsMarketSourceClawhubGlobal: 'ClawHub(全球)',
|
||||
skillsMarketSourceClawhubChina: 'ClawHub(中國)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub(騰訊)',
|
||||
skillsScore: '評分',
|
||||
versionUnsupportedTitle: '當前版本暫不支援該功能',
|
||||
|
||||
@@ -677,7 +677,8 @@ const message = {
|
||||
skillsMarket: '技能市场',
|
||||
skillsMarketHint: '请选择来源并搜索技能',
|
||||
skillsMarketEmpty: '未找到相关技能',
|
||||
skillsMarketSourceClawhub: 'ClawHub(官方)',
|
||||
skillsMarketSourceClawhubGlobal: 'ClawHub(全球)',
|
||||
skillsMarketSourceClawhubChina: 'ClawHub(中国)',
|
||||
skillsMarketSourceSkillhub: 'SkillHub(腾讯)',
|
||||
skillsScore: '评分',
|
||||
versionUnsupportedTitle: '当前版本暂不支持该功能',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<VersionSupport v-if="!supported" :min-version="openclawMinSupportedVersion" />
|
||||
<div v-else>
|
||||
<el-radio-group v-model="mode" class="view-switch">
|
||||
<el-radio-button label="installed">{{ t('app.installed') }}</el-radio-button>
|
||||
<el-radio-group v-model="mode" class="view-switch" @change="handleModeChange">
|
||||
<el-radio-button label="market">{{ t('aiTools.agents.skillsMarket') }}</el-radio-button>
|
||||
<el-radio-button label="installed">{{ t('app.installed') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<div v-loading="loading" class="skills-content">
|
||||
@@ -25,7 +25,11 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-select v-model="marketSource" class="p-w-200" @change="handleMarketSourceChange">
|
||||
<el-option :value="'clawhub'" :label="t('aiTools.agents.skillsMarketSourceClawhub')" />
|
||||
<el-option
|
||||
:value="'clawhub-global'"
|
||||
:label="t('aiTools.agents.skillsMarketSourceClawhubGlobal')"
|
||||
/>
|
||||
<el-option :value="'clawhub-cn'" :label="t('aiTools.agents.skillsMarketSourceClawhubChina')" />
|
||||
<el-option :value="'skillhub'" :label="t('aiTools.agents.skillsMarketSourceSkillhub')" />
|
||||
</el-select>
|
||||
<el-input
|
||||
@@ -150,7 +154,7 @@ import VersionSupport from './components/version-support.vue';
|
||||
|
||||
type SkillGroupKey = 'builtIn' | 'external' | 'workspace' | 'extra' | 'other';
|
||||
type SkillViewMode = 'installed' | 'market';
|
||||
type SkillMarketSource = 'clawhub' | 'skillhub';
|
||||
type SkillMarketSource = 'clawhub-global' | 'clawhub-cn' | 'skillhub';
|
||||
|
||||
const openclawMinSupportedVersion = '2026.3.23';
|
||||
const props = defineProps<{
|
||||
@@ -161,10 +165,11 @@ const { t } = useI18n();
|
||||
const { isIntl } = useGlobalStore();
|
||||
const loading = ref(false);
|
||||
const searching = ref(false);
|
||||
const mode = ref<SkillViewMode>('installed');
|
||||
const mode = ref<SkillViewMode>('market');
|
||||
const installedKeyword = ref('');
|
||||
const marketKeyword = ref('');
|
||||
const marketSource = ref<SkillMarketSource>(isIntl.value ? 'clawhub' : 'skillhub');
|
||||
const getDefaultMarketSource = (): SkillMarketSource => (isIntl.value ? 'clawhub-global' : 'clawhub-cn');
|
||||
const marketSource = ref<SkillMarketSource>(getDefaultMarketSource());
|
||||
const marketSearched = ref(false);
|
||||
const agentId = ref(0);
|
||||
const skills = ref<AI.AgentSkillItem[]>([]);
|
||||
@@ -172,6 +177,7 @@ const marketResults = ref<AI.AgentSkillSearchItem[]>([]);
|
||||
const updatingSkill = ref('');
|
||||
const installingSkill = ref('');
|
||||
const taskLogRef = ref<InstanceType<typeof TaskLog>>();
|
||||
const installedLoaded = ref(false);
|
||||
const supported = computed(() => isOpenclawCurrentHTTPVersion(props.appVersion));
|
||||
|
||||
const groupTagLabels = computed<Record<SkillGroupKey, string>>(() => ({
|
||||
@@ -251,6 +257,7 @@ const loadSkills = async () => {
|
||||
try {
|
||||
const res = await listAgentSkills({ agentId: agentId.value });
|
||||
skills.value = res.data || [];
|
||||
installedLoaded.value = true;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -279,13 +286,21 @@ const handleMarketSourceChange = () => {
|
||||
marketSearched.value = false;
|
||||
};
|
||||
|
||||
const handleModeChange = async (value: SkillViewMode) => {
|
||||
if (value !== 'installed' || installedLoaded.value) {
|
||||
return;
|
||||
}
|
||||
await loadSkills();
|
||||
};
|
||||
|
||||
const load = async (id: number) => {
|
||||
agentId.value = id;
|
||||
mode.value = 'installed';
|
||||
marketSource.value = isIntl.value ? 'clawhub' : 'skillhub';
|
||||
mode.value = 'market';
|
||||
marketSource.value = getDefaultMarketSource();
|
||||
marketResults.value = [];
|
||||
marketSearched.value = false;
|
||||
await loadSkills();
|
||||
skills.value = [];
|
||||
installedLoaded.value = false;
|
||||
};
|
||||
|
||||
const toggleSkill = async (skill: AI.AgentSkillItem, enabled: boolean) => {
|
||||
@@ -326,7 +341,11 @@ const installSkill = async (skill: AI.AgentSkillSearchItem) => {
|
||||
};
|
||||
|
||||
const handleTaskClose = async () => {
|
||||
await loadSkills();
|
||||
if (mode.value === 'installed') {
|
||||
await loadSkills();
|
||||
return;
|
||||
}
|
||||
installedLoaded.value = false;
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
|
||||
Reference in New Issue
Block a user