Fix/frontend review findings #3
@@ -0,0 +1,36 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { usePluginStore } from '@/stores/plugin'
|
||||||
|
import { useSkillStore } from '@/stores/skill'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const skillStore = useSkillStore()
|
||||||
|
const pluginStore = usePluginStore()
|
||||||
|
const isPlugin = computed(() => route.name === 'plugins')
|
||||||
|
|
||||||
|
onMounted(() => { if (isPlugin.value) void pluginStore.loadPlugins(); else void skillStore.loadSkills() })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="sidebar-panel">
|
||||||
|
<div v-if="isPlugin" class="sidebar-list">
|
||||||
|
<button v-for="plugin in pluginStore.plugins" :key="plugin.plugin_id" class="sidebar-list-item extension-item"
|
||||||
|
:class="{ active: pluginStore.selectedPluginId === plugin.plugin_id }" @click="pluginStore.selectPlugin(plugin.plugin_id)">
|
||||||
|
<span>{{ plugin.icon || '🧩' }}</span><span><strong>{{ plugin.name }}</strong><small>{{ plugin.status }}</small></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-else class="sidebar-list">
|
||||||
|
<button v-for="skill in skillStore.skills" :key="skill.skill_id" class="sidebar-list-item extension-item"
|
||||||
|
:class="{ active: skillStore.selectedSkillId === skill.skill_id }" @click="skillStore.selectSkill(skill.skill_id)">
|
||||||
|
<span>{{ skill.icon || '⚡' }}</span><span><strong>{{ skill.name }}</strong><small>{{ skill.status }}</small></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.extension-item { display: grid; grid-template-columns: auto 1fr; align-items: center; gap: var(--space-sm); width: 100%; text-align: left; }
|
||||||
|
.extension-item strong, .extension-item small { display: block; }
|
||||||
|
.extension-item small { color: var(--color-text-tertiary); }
|
||||||
|
</style>
|
||||||
@@ -246,6 +246,7 @@ export interface Plugin {
|
|||||||
status: PluginStatus
|
status: PluginStatus
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
permissions: string[]
|
permissions: string[]
|
||||||
|
granted_permissions?: string[]
|
||||||
contributions: PluginContribution[]
|
contributions: PluginContribution[]
|
||||||
backend_type?: 'mcp' | 'internal_rpc' | 'none'
|
backend_type?: 'mcp' | 'internal_rpc' | 'none'
|
||||||
transport?: 'stdio' | 'http' | 'none'
|
transport?: 'stdio' | 'http' | 'none'
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { usePluginStore } from '@/stores/plugin'
|
||||||
|
|
||||||
|
const pluginStore = usePluginStore()
|
||||||
|
const actionError = ref('')
|
||||||
|
onMounted(() => { void pluginStore.loadPlugins() })
|
||||||
|
|
||||||
|
async function install() { const path = prompt('请输入 Plugin Package 路径')?.trim(); if (!path) return; try { await pluginStore.installPlugin(path) } catch (error) { actionError.value = error instanceof Error ? error.message : '安装失败' } }
|
||||||
|
async function toggle(id: string, enabled: boolean) { try { enabled ? await pluginStore.disablePlugin(id) : await pluginStore.enablePlugin(id) } catch (error) { actionError.value = error instanceof Error ? error.message : '状态更新失败' } }
|
||||||
|
async function grant(id: string, permissions: string[]) { if (!confirm(`将授权:${permissions.join('、')}。是否继续?`)) return; try { await pluginStore.grantPermissions(id, permissions) } catch (error) { actionError.value = error instanceof Error ? error.message : '授权失败' } }
|
||||||
|
async function uninstall(id: string, name: string) { if (!confirm(`卸载“${name}”将移除其全部 Contribution,是否继续?`)) return; try { await pluginStore.uninstallPlugin(id) } catch (error) { actionError.value = error instanceof Error ? error.message : '卸载失败' } }
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="feature-page">
|
||||||
|
<header class="feature-header"><div><h1>Plugin 管理</h1><p>管理插件生命周期、权限和受控 Contribution。</p></div><button class="button-primary" @click="install">安装 Plugin</button></header>
|
||||||
|
<div v-if="pluginStore.error || actionError" class="error-banner">{{ pluginStore.error || actionError }}</div>
|
||||||
|
<div v-if="pluginStore.selectedPlugin" class="panel">
|
||||||
|
<div class="detail-head"><div><span class="badge" :class="{ success: pluginStore.selectedPlugin.status === 'ready', error: pluginStore.selectedPlugin.status === 'error', warning: pluginStore.selectedPlugin.status === 'permission_required' }">{{ pluginStore.selectedPlugin.status }}</span><h2>{{ pluginStore.selectedPlugin.icon }} {{ pluginStore.selectedPlugin.name }}</h2><p class="muted">v{{ pluginStore.selectedPlugin.version }} · {{ pluginStore.selectedPlugin.backend_type || 'none' }}/{{ pluginStore.selectedPlugin.transport || 'none' }}</p></div><div class="inline-actions"><button v-if="pluginStore.selectedPlugin.status === 'permission_required'" class="button-primary" @click="grant(pluginStore.selectedPlugin.plugin_id, pluginStore.selectedPlugin.permissions)">授权权限</button><button class="button-secondary" @click="toggle(pluginStore.selectedPlugin.plugin_id, pluginStore.selectedPlugin.enabled)">{{ pluginStore.selectedPlugin.enabled ? '停用' : '启用' }}</button><button class="button-danger" @click="uninstall(pluginStore.selectedPlugin.plugin_id, pluginStore.selectedPlugin.name)">卸载</button></div></div>
|
||||||
|
<p class="description">{{ pluginStore.selectedPlugin.description }}</p>
|
||||||
|
<div class="detail-grid"><div><h3>权限</h3><div class="tag-list"><span v-for="permission in pluginStore.selectedPlugin.permissions" :key="permission" class="badge warning">{{ permission }}</span></div></div><div><h3>Contribution</h3><div class="contribution-list"><div v-for="item in pluginStore.selectedPlugin.contributions" :key="item.id" class="item-card"><span class="badge info">{{ item.type }}</span><strong>{{ item.name }}</strong><p class="subtle">{{ item.description || item.id }}</p></div></div></div></div>
|
||||||
|
<div v-if="pluginStore.selectedPlugin.last_error" class="error-banner last-error">{{ pluginStore.selectedPlugin.last_error }}</div>
|
||||||
|
<div v-if="pluginStore.selectedPlugin.dependent_skills?.length" class="notice-banner last-error">依赖此插件的 Skill:{{ pluginStore.selectedPlugin.dependent_skills.join('、') }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="feature-grid"><article v-for="plugin in pluginStore.plugins" :key="plugin.plugin_id" class="item-card extension-card" @click="pluginStore.selectPlugin(plugin.plugin_id)"><div class="extension-title"><span class="icon">{{ plugin.icon || '🧩' }}</span><div><strong>{{ plugin.name }}</strong><p>v{{ plugin.version }}</p></div><span class="badge" :class="{ success: plugin.status === 'ready', error: plugin.status === 'error', warning: plugin.status === 'permission_required' }">{{ plugin.status }}</span></div><p class="muted">{{ plugin.description }}</p><p class="subtle">{{ plugin.permissions.length }} 项权限 · {{ plugin.contributions.length }} 项 Contribution</p></article></div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.detail-head, .extension-title { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-md); }
|
||||||
|
.detail-head h2 { margin-top: var(--space-sm); }
|
||||||
|
.description { margin: var(--space-xl) 0; line-height: var(--line-height-relaxed); }
|
||||||
|
.detail-grid { display: grid; grid-template-columns: minmax(220px, .7fr) minmax(320px, 1.3fr); gap: var(--space-xl); }
|
||||||
|
.detail-grid h3 { margin-bottom: var(--space-sm); }
|
||||||
|
.contribution-list { display: grid; gap: var(--space-sm); }
|
||||||
|
.contribution-list .item-card { display: grid; gap: var(--space-xs); }
|
||||||
|
.last-error { margin: var(--space-xl) 0 0; }
|
||||||
|
.extension-card { cursor: pointer; }
|
||||||
|
.extension-card > p { margin-top: var(--space-md); }
|
||||||
|
.extension-title { align-items: center; }
|
||||||
|
.extension-title .icon { font-size: 28px; }
|
||||||
|
.extension-title p { color: var(--color-text-tertiary); font-size: var(--font-size-xs); }
|
||||||
|
@media (max-width: 800px) { .detail-grid { grid-template-columns: 1fr; } }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { useSkillStore } from '@/stores/skill'
|
||||||
|
|
||||||
|
const skillStore = useSkillStore()
|
||||||
|
const actionError = ref('')
|
||||||
|
onMounted(() => { void skillStore.loadSkills() })
|
||||||
|
|
||||||
|
async function install() {
|
||||||
|
const path = prompt('请输入 Skill Package 路径')?.trim()
|
||||||
|
if (!path) return
|
||||||
|
try { await skillStore.installSkill(path) } catch (error) { actionError.value = error instanceof Error ? error.message : '安装失败' }
|
||||||
|
}
|
||||||
|
async function toggle(skillId: string, enabled: boolean) {
|
||||||
|
try { enabled ? await skillStore.disableSkill(skillId) : await skillStore.enableSkill(skillId) } catch (error) { actionError.value = error instanceof Error ? error.message : '状态更新失败' }
|
||||||
|
}
|
||||||
|
async function uninstall(skillId: string, name: string) {
|
||||||
|
if (!confirm(`确定卸载 Skill“${name}”吗?`)) return
|
||||||
|
try { await skillStore.uninstallSkill(skillId) } catch (error) { actionError.value = error instanceof Error ? error.message : '卸载失败' }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="feature-page">
|
||||||
|
<header class="feature-header"><div><h1>Skill 管理</h1><p>查看工作流使用的 Tool、权限、检索配置和模型要求。</p></div><button class="button-primary" @click="install">安装 Skill</button></header>
|
||||||
|
<div v-if="skillStore.error || actionError" class="error-banner">{{ skillStore.error || actionError }}</div>
|
||||||
|
<div v-if="skillStore.selectedSkill" class="panel detail-panel">
|
||||||
|
<div class="detail-head"><div><span class="badge" :class="{ success: skillStore.selectedSkill.status === 'ready', error: skillStore.selectedSkill.status === 'error', warning: skillStore.selectedSkill.status.includes('missing') }">{{ skillStore.selectedSkill.status }}</span><h2>{{ skillStore.selectedSkill.icon }} {{ skillStore.selectedSkill.name }}</h2><p class="muted">v{{ skillStore.selectedSkill.version }} · {{ skillStore.selectedSkill.author || '未知作者' }}</p></div><div class="inline-actions"><button class="button-secondary" @click="toggle(skillStore.selectedSkill.skill_id, skillStore.selectedSkill.enabled)">{{ skillStore.selectedSkill.enabled ? '停用' : '启用' }}</button><button class="button-danger" @click="uninstall(skillStore.selectedSkill.skill_id, skillStore.selectedSkill.name)">卸载</button></div></div>
|
||||||
|
<p class="description">{{ skillStore.selectedSkill.description }}</p>
|
||||||
|
<div class="detail-grid"><div><h3>工具</h3><div class="tag-list"><span v-for="tool in skillStore.selectedSkill.tools" :key="tool" class="badge info">{{ tool }}</span></div></div><div><h3>权限</h3><div class="tag-list"><span v-for="permission in skillStore.selectedSkill.permissions" :key="permission" class="badge warning">{{ permission }}</span></div></div><div><h3>检索配置</h3><pre>{{ JSON.stringify(skillStore.selectedSkill.retrieval_config, null, 2) }}</pre></div><div><h3>模型能力</h3><div class="tag-list"><span v-for="cap in skillStore.selectedSkill.model_requirements?.capabilities" :key="cap" class="badge">{{ cap }}</span></div></div></div>
|
||||||
|
<div v-if="skillStore.selectedSkill.missing_dependencies?.length" class="error-banner dependencies">缺失依赖:{{ skillStore.selectedSkill.missing_dependencies.join('、') }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="feature-grid"><article v-for="skill in skillStore.skills" :key="skill.skill_id" class="item-card extension-card" @click="skillStore.selectSkill(skill.skill_id)"><div class="extension-title"><span class="icon">{{ skill.icon || '⚡' }}</span><div><strong>{{ skill.name }}</strong><p>v{{ skill.version }}</p></div><span class="badge" :class="{ success: skill.status === 'ready', warning: skill.status === 'dependency_missing' }">{{ skill.status }}</span></div><p class="muted">{{ skill.description }}</p><div class="tag-list"><span v-for="permission in skill.permissions.slice(0, 3)" :key="permission" class="badge">{{ permission }}</span></div></article></div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.detail-head, .extension-title { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-md); }
|
||||||
|
.detail-head h2 { margin-top: var(--space-sm); }
|
||||||
|
.description { margin: var(--space-xl) 0; line-height: var(--line-height-relaxed); }
|
||||||
|
.detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-xl); }
|
||||||
|
.detail-grid h3 { margin-bottom: var(--space-sm); }
|
||||||
|
pre { padding: var(--space-md); border-radius: var(--radius-md); background: var(--color-background-secondary); }
|
||||||
|
.dependencies { margin: var(--space-xl) 0 0; }
|
||||||
|
.extension-card { cursor: pointer; }
|
||||||
|
.extension-card > p { margin: var(--space-md) 0; }
|
||||||
|
.extension-title { align-items: center; }
|
||||||
|
.extension-title .icon { font-size: 28px; }
|
||||||
|
.extension-title p { color: var(--color-text-tertiary); font-size: var(--font-size-xs); }
|
||||||
|
</style>
|
||||||
@@ -20,7 +20,8 @@ function toPlugin(plugin: ApiPlugin): Plugin {
|
|||||||
description: manifest.description,
|
description: manifest.description,
|
||||||
status: plugin.status,
|
status: plugin.status,
|
||||||
enabled: plugin.enabled,
|
enabled: plugin.enabled,
|
||||||
permissions: plugin.granted_permissions,
|
permissions: manifest.permissions,
|
||||||
|
granted_permissions: plugin.granted_permissions,
|
||||||
contributions,
|
contributions,
|
||||||
backend_type: manifest.backend.type,
|
backend_type: manifest.backend.type,
|
||||||
transport: manifest.backend.transport,
|
transport: manifest.backend.transport,
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import type { Plugin } from '@/contracts'
|
import type { Plugin } from '@/contracts'
|
||||||
import { mockPlugins } from '@/services/pluginService'
|
import * as pluginService from '@/services/pluginService'
|
||||||
|
|
||||||
export const usePluginStore = defineStore('plugin', () => {
|
export const usePluginStore = defineStore('plugin', () => {
|
||||||
const plugins = ref<Plugin[]>(mockPlugins)
|
const plugins = ref<Plugin[]>(pluginService.mockPlugins)
|
||||||
const selectedPluginId = ref<string | null>(null)
|
const selectedPluginId = ref<string | null>(null)
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
|
||||||
const selectedPlugin = computed(() =>
|
const selectedPlugin = computed(() =>
|
||||||
plugins.value.find((p) => p.plugin_id === selectedPluginId.value) || null
|
plugins.value.find((p) => p.plugin_id === selectedPluginId.value) || null
|
||||||
@@ -19,8 +20,10 @@ export const usePluginStore = defineStore('plugin', () => {
|
|||||||
async function loadPlugins() {
|
async function loadPlugins() {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
try {
|
try {
|
||||||
const { listPlugins } = await import('@/services/pluginService')
|
plugins.value = await pluginService.listPlugins()
|
||||||
plugins.value = await listPlugins()
|
error.value = null
|
||||||
|
} catch (reason) {
|
||||||
|
error.value = reason instanceof Error ? reason.message : 'Plugin 加载失败'
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -30,23 +33,34 @@ export const usePluginStore = defineStore('plugin', () => {
|
|||||||
selectedPluginId.value = pluginId
|
selectedPluginId.value = pluginId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function installPlugin(packagePath: string) {
|
||||||
|
const installed = await pluginService.installPlugin(packagePath)
|
||||||
|
const index = plugins.value.findIndex((plugin) => plugin.plugin_id === installed.plugin_id)
|
||||||
|
if (index >= 0) plugins.value[index] = installed
|
||||||
|
else plugins.value.unshift(installed)
|
||||||
|
selectedPluginId.value = installed.plugin_id
|
||||||
|
}
|
||||||
|
|
||||||
|
async function grantPermissions(pluginId: string, permissions: string[]) {
|
||||||
|
const updated = await pluginService.grantPluginPermissions(pluginId, permissions)
|
||||||
|
const index = plugins.value.findIndex((plugin) => plugin.plugin_id === pluginId)
|
||||||
|
if (index >= 0) plugins.value[index] = updated
|
||||||
|
}
|
||||||
|
|
||||||
async function enablePlugin(pluginId: string) {
|
async function enablePlugin(pluginId: string) {
|
||||||
const plugin = plugins.value.find((p) => p.plugin_id === pluginId)
|
const updated = await pluginService.enablePlugin(pluginId)
|
||||||
if (plugin) {
|
const index = plugins.value.findIndex((plugin) => plugin.plugin_id === pluginId)
|
||||||
plugin.enabled = true
|
if (index >= 0) plugins.value[index] = updated
|
||||||
plugin.status = 'ready'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function disablePlugin(pluginId: string) {
|
async function disablePlugin(pluginId: string) {
|
||||||
const plugin = plugins.value.find((p) => p.plugin_id === pluginId)
|
const updated = await pluginService.disablePlugin(pluginId)
|
||||||
if (plugin) {
|
const index = plugins.value.findIndex((plugin) => plugin.plugin_id === pluginId)
|
||||||
plugin.enabled = false
|
if (index >= 0) plugins.value[index] = updated
|
||||||
plugin.status = 'disabled'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uninstallPlugin(pluginId: string) {
|
async function uninstallPlugin(pluginId: string) {
|
||||||
|
await pluginService.uninstallPlugin(pluginId)
|
||||||
const idx = plugins.value.findIndex((p) => p.plugin_id === pluginId)
|
const idx = plugins.value.findIndex((p) => p.plugin_id === pluginId)
|
||||||
if (idx > -1) plugins.value.splice(idx, 1)
|
if (idx > -1) plugins.value.splice(idx, 1)
|
||||||
if (selectedPluginId.value === pluginId) selectedPluginId.value = null
|
if (selectedPluginId.value === pluginId) selectedPluginId.value = null
|
||||||
@@ -60,8 +74,11 @@ export const usePluginStore = defineStore('plugin', () => {
|
|||||||
readyPlugins,
|
readyPlugins,
|
||||||
errorPlugins,
|
errorPlugins,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
error,
|
||||||
loadPlugins,
|
loadPlugins,
|
||||||
selectPlugin,
|
selectPlugin,
|
||||||
|
installPlugin,
|
||||||
|
grantPermissions,
|
||||||
enablePlugin,
|
enablePlugin,
|
||||||
disablePlugin,
|
disablePlugin,
|
||||||
uninstallPlugin,
|
uninstallPlugin,
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import type { Skill } from '@/contracts'
|
import type { Skill } from '@/contracts'
|
||||||
import { mockSkills } from '@/services/skillService'
|
import * as skillService from '@/services/skillService'
|
||||||
|
|
||||||
export const useSkillStore = defineStore('skill', () => {
|
export const useSkillStore = defineStore('skill', () => {
|
||||||
const skills = ref<Skill[]>(mockSkills)
|
const skills = ref<Skill[]>(skillService.mockSkills)
|
||||||
const selectedSkillId = ref<string | null>(null)
|
const selectedSkillId = ref<string | null>(null)
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
|
||||||
const selectedSkill = computed(() =>
|
const selectedSkill = computed(() =>
|
||||||
skills.value.find((s) => s.skill_id === selectedSkillId.value) || null
|
skills.value.find((s) => s.skill_id === selectedSkillId.value) || null
|
||||||
@@ -19,8 +20,10 @@ export const useSkillStore = defineStore('skill', () => {
|
|||||||
async function loadSkills() {
|
async function loadSkills() {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
try {
|
try {
|
||||||
const { listSkills } = await import('@/services/skillService')
|
skills.value = await skillService.listSkills()
|
||||||
skills.value = await listSkills()
|
error.value = null
|
||||||
|
} catch (reason) {
|
||||||
|
error.value = reason instanceof Error ? reason.message : 'Skill 加载失败'
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -30,23 +33,28 @@ export const useSkillStore = defineStore('skill', () => {
|
|||||||
selectedSkillId.value = skillId
|
selectedSkillId.value = skillId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function installSkill(packagePath: string) {
|
||||||
|
const installed = await skillService.installSkill(packagePath)
|
||||||
|
const index = skills.value.findIndex((skill) => skill.skill_id === installed.skill_id)
|
||||||
|
if (index >= 0) skills.value[index] = installed
|
||||||
|
else skills.value.unshift(installed)
|
||||||
|
selectedSkillId.value = installed.skill_id
|
||||||
|
}
|
||||||
|
|
||||||
async function enableSkill(skillId: string) {
|
async function enableSkill(skillId: string) {
|
||||||
const skill = skills.value.find((s) => s.skill_id === skillId)
|
const updated = await skillService.enableSkill(skillId)
|
||||||
if (skill) {
|
const index = skills.value.findIndex((skill) => skill.skill_id === skillId)
|
||||||
skill.enabled = true
|
if (index >= 0) skills.value[index] = updated
|
||||||
skill.status = 'ready'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function disableSkill(skillId: string) {
|
async function disableSkill(skillId: string) {
|
||||||
const skill = skills.value.find((s) => s.skill_id === skillId)
|
const updated = await skillService.disableSkill(skillId)
|
||||||
if (skill) {
|
const index = skills.value.findIndex((skill) => skill.skill_id === skillId)
|
||||||
skill.enabled = false
|
if (index >= 0) skills.value[index] = updated
|
||||||
skill.status = 'disabled'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uninstallSkill(skillId: string) {
|
async function uninstallSkill(skillId: string) {
|
||||||
|
await skillService.uninstallSkill(skillId)
|
||||||
const idx = skills.value.findIndex((s) => s.skill_id === skillId)
|
const idx = skills.value.findIndex((s) => s.skill_id === skillId)
|
||||||
if (idx > -1) skills.value.splice(idx, 1)
|
if (idx > -1) skills.value.splice(idx, 1)
|
||||||
if (selectedSkillId.value === skillId) selectedSkillId.value = null
|
if (selectedSkillId.value === skillId) selectedSkillId.value = null
|
||||||
@@ -60,8 +68,10 @@ export const useSkillStore = defineStore('skill', () => {
|
|||||||
installedSkills,
|
installedSkills,
|
||||||
readySkills,
|
readySkills,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
error,
|
||||||
loadSkills,
|
loadSkills,
|
||||||
selectSkill,
|
selectSkill,
|
||||||
|
installSkill,
|
||||||
enableSkill,
|
enableSkill,
|
||||||
disableSkill,
|
disableSkill,
|
||||||
uninstallSkill,
|
uninstallSkill,
|
||||||
|
|||||||
Reference in New Issue
Block a user