feat(gui): streamline evolution panel and group settings (U6)
- EvolutionView: simplify from 6 sub-panels to 3 tabs (概览+指标, 经验+坑点, 用量) - SettingsView: group into 4 tabs (LLM, 技能, 知识库, 系统), each with independent save - SkillsView: replace hardcoded colors with Design Tokens - All three views: replace hardcoded colors with Design Token references
This commit is contained in:
parent
3dc5c68135
commit
db216a5cc4
|
|
@ -1,65 +1,32 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="evolution-container">
|
<div class="evolution-container">
|
||||||
<div class="evolution-sidebar">
|
<a-tabs v-model:activeKey="activeTab" class="evolution-tabs">
|
||||||
<div class="evolution-sidebar__header">
|
<a-tab-pane key="overview" tab="概览+指标">
|
||||||
<h2>自进化</h2>
|
<DashboardOverview />
|
||||||
<a-spin v-if="store.isLoading" size="small" />
|
</a-tab-pane>
|
||||||
</div>
|
<a-tab-pane key="experiences" tab="经验+坑点">
|
||||||
<a-menu mode="inline" :selected-keys="[currentRoute]" class="evolution-sidebar__menu">
|
<div class="evolution-panels">
|
||||||
<a-menu-item key="overview" @click="navigate('overview')">
|
<ExperienceTimeline :experiences="store.experiences" />
|
||||||
<DashboardOutlined /> 概览
|
<PitfallPanel :warnings="store.pitfalls" />
|
||||||
</a-menu-item>
|
</div>
|
||||||
<a-menu-item key="experiences" @click="navigate('experiences')">
|
</a-tab-pane>
|
||||||
<HistoryOutlined /> 经验记录
|
<a-tab-pane key="usage" tab="用量">
|
||||||
</a-menu-item>
|
<UsagePanel />
|
||||||
<a-menu-item key="metrics" @click="navigate('metrics')">
|
</a-tab-pane>
|
||||||
<LineChartOutlined /> 指标趋势
|
</a-tabs>
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="pitfalls" @click="navigate('pitfalls')">
|
|
||||||
<WarningOutlined /> 避坑预警
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="optimizations" @click="navigate('optimizations')">
|
|
||||||
<RocketOutlined /> 路径优化
|
|
||||||
</a-menu-item>
|
|
||||||
<a-menu-item key="usage" @click="navigate('usage')">
|
|
||||||
<CloudServerOutlined /> 用量统计
|
|
||||||
</a-menu-item>
|
|
||||||
</a-menu>
|
|
||||||
</div>
|
|
||||||
<div class="evolution-content">
|
|
||||||
<router-view />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, onUnmounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
|
||||||
import { Spin as ASpin, Menu as AMenu, MenuItem as AMenuItem } from 'ant-design-vue'
|
|
||||||
import {
|
|
||||||
DashboardOutlined,
|
|
||||||
HistoryOutlined,
|
|
||||||
LineChartOutlined,
|
|
||||||
WarningOutlined,
|
|
||||||
RocketOutlined,
|
|
||||||
CloudServerOutlined,
|
|
||||||
} from '@ant-design/icons-vue'
|
|
||||||
import { useEvolutionStore } from '@/stores/evolution'
|
import { useEvolutionStore } from '@/stores/evolution'
|
||||||
|
import DashboardOverview from '@/components/evolution/DashboardOverview.vue'
|
||||||
|
import ExperienceTimeline from '@/components/evolution/ExperienceTimeline.vue'
|
||||||
|
import PitfallPanel from '@/components/evolution/PitfallPanel.vue'
|
||||||
|
import UsagePanel from '@/components/evolution/UsagePanel.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const route = useRoute()
|
|
||||||
const store = useEvolutionStore()
|
const store = useEvolutionStore()
|
||||||
|
const activeTab = ref('overview')
|
||||||
const currentRoute = computed(() => {
|
|
||||||
const path = route.path
|
|
||||||
if (path === '/evolution' || path === '/evolution/') return 'overview'
|
|
||||||
const segment = path.split('/').pop()
|
|
||||||
return segment || 'overview'
|
|
||||||
})
|
|
||||||
|
|
||||||
function navigate(key: string) {
|
|
||||||
router.push(`/evolution/${key}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
store.loadExperiences()
|
store.loadExperiences()
|
||||||
|
|
@ -76,40 +43,18 @@ onUnmounted(() => {
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.evolution-container {
|
.evolution-container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
overflow-y: auto;
|
||||||
overflow: hidden;
|
padding: var(--space-3) var(--space-4);
|
||||||
|
background: var(--bg-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.evolution-sidebar {
|
.evolution-tabs {
|
||||||
width: 180px;
|
height: 100%;
|
||||||
flex-shrink: 0;
|
}
|
||||||
background: #fff;
|
|
||||||
border-right: 1px solid #f0f0f0;
|
.evolution-panels {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
gap: var(--space-4);
|
||||||
|
|
||||||
.evolution-sidebar__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 16px 16px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.evolution-sidebar__header h2 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.evolution-sidebar__menu {
|
|
||||||
flex: 1;
|
|
||||||
border-inline-end: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.evolution-content {
|
|
||||||
flex: 1;
|
|
||||||
overflow: auto;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,144 +1,144 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="settings-view">
|
<div class="settings-view">
|
||||||
<a-form layout="vertical" class="settings-form">
|
<a-tabs v-model:activeKey="activeTab" class="settings-tabs">
|
||||||
<!-- LLM 配置 -->
|
<a-tab-pane key="llm" tab="LLM 配置">
|
||||||
<a-divider orientation="left">LLM 配置</a-divider>
|
<a-form layout="vertical" class="settings-form">
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="Provider">
|
<a-form-item label="Provider">
|
||||||
<a-select v-model:value="settingsStore.llm.provider" placeholder="选择 LLM 提供商">
|
<a-select v-model:value="settingsStore.llm.provider" placeholder="选择 LLM 提供商">
|
||||||
<a-select-option value="anthropic">Anthropic</a-select-option>
|
<a-select-option value="anthropic">Anthropic</a-select-option>
|
||||||
<a-select-option value="openai">OpenAI</a-select-option>
|
<a-select-option value="openai">OpenAI</a-select-option>
|
||||||
<a-select-option value="gemini">Gemini</a-select-option>
|
<a-select-option value="gemini">Gemini</a-select-option>
|
||||||
<a-select-option value="custom">自定义</a-select-option>
|
<a-select-option value="custom">自定义</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="模型">
|
<a-form-item label="模型">
|
||||||
<a-input v-model:value="settingsStore.llm.model" placeholder="例如: claude-sonnet-4-20250514" />
|
<a-input v-model:value="settingsStore.llm.model" placeholder="例如: claude-sonnet-4-20250514" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="API Key">
|
<a-form-item label="API Key">
|
||||||
<a-input-password v-model:value="settingsStore.llm.api_key" placeholder="输入 API Key" />
|
<a-input-password v-model:value="settingsStore.llm.api_key" placeholder="输入 API Key" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="Base URL">
|
<a-form-item label="Base URL">
|
||||||
<a-input v-model:value="settingsStore.llm.base_url" placeholder="自定义 API 地址(可选)" />
|
<a-input v-model:value="settingsStore.llm.base_url" placeholder="自定义 API 地址(可选)" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
<a-button type="primary" :loading="settingsStore.isSaving" @click="handleSave">保存 LLM 配置</a-button>
|
||||||
|
</a-form>
|
||||||
|
</a-tab-pane>
|
||||||
|
|
||||||
<!-- 技能配置 -->
|
<a-tab-pane key="skills" tab="技能管理">
|
||||||
<a-divider orientation="left">技能配置</a-divider>
|
<a-form layout="vertical" class="settings-form">
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="默认技能">
|
<a-form-item label="默认技能">
|
||||||
<a-input v-model:value="settingsStore.skillSettings.default_skill" placeholder="留空则使用自动路由" />
|
<a-input v-model:value="settingsStore.skillSettings.default_skill" placeholder="留空则使用自动路由" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="自动路由">
|
<a-form-item label="自动路由">
|
||||||
<a-switch v-model:checked="settingsStore.skillSettings.auto_routing" />
|
<a-switch v-model:checked="settingsStore.skillSettings.auto_routing" />
|
||||||
<span style="margin-left: 8px; color: #999">启用后自动将消息路由到最匹配的技能</span>
|
<span class="settings-form__hint">启用后自动将消息路由到最匹配的技能</span>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
<a-button type="primary" :loading="settingsStore.isSaving" @click="handleSave">保存技能配置</a-button>
|
||||||
|
</a-form>
|
||||||
|
</a-tab-pane>
|
||||||
|
|
||||||
<!-- 知识库配置 -->
|
<a-tab-pane key="knowledge" tab="知识库设置">
|
||||||
<a-divider orientation="left">知识库配置</a-divider>
|
<a-form layout="vertical" class="settings-form">
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="8">
|
<a-col :span="8">
|
||||||
<a-form-item label="默认信息源">
|
<a-form-item label="默认信息源">
|
||||||
<a-select
|
<a-select
|
||||||
v-model:value="settingsStore.kbSettings.default_sources"
|
v-model:value="settingsStore.kbSettings.default_sources"
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder="选择默认信息源"
|
placeholder="选择默认信息源"
|
||||||
>
|
>
|
||||||
<a-select-option value="local">本地文档</a-select-option>
|
<a-select-option value="local">本地文档</a-select-option>
|
||||||
<a-select-option value="feishu">飞书</a-select-option>
|
<a-select-option value="feishu">飞书</a-select-option>
|
||||||
<a-select-option value="confluence">Confluence</a-select-option>
|
<a-select-option value="confluence">Confluence</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="8">
|
<a-col :span="8">
|
||||||
<a-form-item label="检索数量 (Top K)">
|
<a-form-item label="检索数量 (Top K)">
|
||||||
<a-input-number v-model:value="settingsStore.kbSettings.top_k" :min="1" :max="50" style="width: 100%" />
|
<a-input-number v-model:value="settingsStore.kbSettings.top_k" :min="1" :max="50" style="width: 100%" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="8">
|
<a-col :span="8">
|
||||||
<a-form-item label="检索模式">
|
<a-form-item label="检索模式">
|
||||||
<a-select v-model:value="settingsStore.kbSettings.retrieval_mode">
|
<a-select v-model:value="settingsStore.kbSettings.retrieval_mode">
|
||||||
<a-select-option value="standard">标准</a-select-option>
|
<a-select-option value="standard">标准</a-select-option>
|
||||||
<a-select-option value="rerank">重排序</a-select-option>
|
<a-select-option value="rerank">重排序</a-select-option>
|
||||||
<a-select-option value="compression">压缩</a-select-option>
|
<a-select-option value="compression">压缩</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
<a-button type="primary" :loading="settingsStore.isSaving" @click="handleSave">保存知识库配置</a-button>
|
||||||
|
</a-form>
|
||||||
|
</a-tab-pane>
|
||||||
|
|
||||||
<!-- 系统配置 -->
|
<a-tab-pane key="system" tab="系统设置">
|
||||||
<a-divider orientation="left">系统配置</a-divider>
|
<a-form layout="vertical" class="settings-form">
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="8">
|
<a-col :span="8">
|
||||||
<a-form-item label="速率限制 (次/分钟)">
|
<a-form-item label="速率限制 (次/分钟)">
|
||||||
<a-input-number v-model:value="settingsStore.system.rate_limit" :min="1" :max="1000" style="width: 100%" />
|
<a-input-number v-model:value="settingsStore.system.rate_limit" :min="1" :max="1000" style="width: 100%" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="8">
|
<a-col :span="8">
|
||||||
<a-form-item label="日志级别">
|
<a-form-item label="日志级别">
|
||||||
<a-select v-model:value="settingsStore.system.logging_level">
|
<a-select v-model:value="settingsStore.system.logging_level">
|
||||||
<a-select-option value="DEBUG">DEBUG</a-select-option>
|
<a-select-option value="DEBUG">DEBUG</a-select-option>
|
||||||
<a-select-option value="INFO">INFO</a-select-option>
|
<a-select-option value="INFO">INFO</a-select-option>
|
||||||
<a-select-option value="WARNING">WARNING</a-select-option>
|
<a-select-option value="WARNING">WARNING</a-select-option>
|
||||||
<a-select-option value="ERROR">ERROR</a-select-option>
|
<a-select-option value="ERROR">ERROR</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="8">
|
<a-col :span="8">
|
||||||
<a-form-item label="CORS 来源">
|
<a-form-item label="CORS 来源">
|
||||||
<a-select
|
<a-select
|
||||||
v-model:value="settingsStore.system.cors_origins"
|
v-model:value="settingsStore.system.cors_origins"
|
||||||
mode="tags"
|
mode="tags"
|
||||||
placeholder="输入 CORS 来源"
|
placeholder="输入 CORS 来源"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
<a-button type="primary" :loading="settingsStore.isSaving" @click="handleSave">保存系统配置</a-button>
|
||||||
|
</a-form>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
|
||||||
<div class="settings-form__actions">
|
<div v-if="settingsStore.saveSuccess" class="settings-view__alert">
|
||||||
<a-button type="primary" :loading="settingsStore.isSaving" @click="handleSave">
|
<a-alert message="设置已保存" type="success" show-icon />
|
||||||
保存设置
|
</div>
|
||||||
</a-button>
|
<div v-if="settingsStore.error" class="settings-view__alert">
|
||||||
<a-alert
|
<a-alert :message="settingsStore.error" type="error" show-icon />
|
||||||
v-if="settingsStore.saveSuccess"
|
</div>
|
||||||
message="设置已保存"
|
|
||||||
type="success"
|
|
||||||
show-icon
|
|
||||||
style="margin-left: 12px"
|
|
||||||
/>
|
|
||||||
<a-alert
|
|
||||||
v-if="settingsStore.error"
|
|
||||||
:message="settingsStore.error"
|
|
||||||
type="error"
|
|
||||||
show-icon
|
|
||||||
style="margin-left: 12px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</a-form>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import { useSettingsStore } from '@/stores/settings'
|
import { useSettingsStore } from '@/stores/settings'
|
||||||
|
|
||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
|
const activeTab = ref('llm')
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
settingsStore.fetchSettings()
|
settingsStore.fetchSettings()
|
||||||
|
|
@ -157,18 +157,27 @@ async function handleSave(): Promise<void> {
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.settings-view {
|
.settings-view {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 16px 24px;
|
padding: var(--space-4) var(--space-6);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background: #fff;
|
background: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-tabs {
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-form {
|
.settings-form {
|
||||||
max-width: 900px;
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-form__actions {
|
.settings-form__hint {
|
||||||
margin-top: 24px;
|
margin-left: var(--space-2);
|
||||||
display: flex;
|
color: var(--text-tertiary);
|
||||||
align-items: center;
|
font-size: var(--font-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-view__alert {
|
||||||
|
margin-top: var(--space-3);
|
||||||
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,16 @@
|
||||||
{{ cap.display_name }} ({{ cap.skill_count }})
|
{{ cap.display_name }} ({{ cap.skill_count }})
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-button @click="handleRefresh" :loading="skillsStore.isLoading">
|
<a-space>
|
||||||
<template #icon><ReloadOutlined /></template>
|
<a-button type="primary" @click="showInstallModal = true">
|
||||||
刷新
|
<template #icon><PlusOutlined /></template>
|
||||||
</a-button>
|
安装技能
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="handleRefresh" :loading="skillsStore.isLoading">
|
||||||
|
<template #icon><ReloadOutlined /></template>
|
||||||
|
刷新
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a-spin :spinning="skillsStore.isLoading">
|
<a-spin :spinning="skillsStore.isLoading">
|
||||||
|
|
@ -35,13 +41,35 @@
|
||||||
:skill="skillsStore.selectedSkill"
|
:skill="skillsStore.selectedSkill"
|
||||||
@close="handleDetailClose"
|
@close="handleDetailClose"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<a-modal
|
||||||
|
v-model:open="showInstallModal"
|
||||||
|
title="安装技能"
|
||||||
|
:confirm-loading="installing"
|
||||||
|
@ok="handleInstall"
|
||||||
|
ok-text="安装"
|
||||||
|
cancel-text="取消"
|
||||||
|
>
|
||||||
|
<a-form layout="vertical">
|
||||||
|
<a-form-item label="技能名称" required>
|
||||||
|
<a-input v-model:value="installName" placeholder="例如: find-skills" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="来源 URL(可选)">
|
||||||
|
<a-input v-model:value="installSource" placeholder="https://... 或留空自动搜索" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<a-alert v-if="installError" :message="installError" type="error" show-icon style="margin-top: 8px" />
|
||||||
|
<a-alert v-if="installSuccess" :message="`技能 ${installSuccess} 安装成功!`" type="success" show-icon style="margin-top: 8px" />
|
||||||
|
</a-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { ReloadOutlined } from '@ant-design/icons-vue'
|
import { ReloadOutlined, PlusOutlined } from '@ant-design/icons-vue'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
import { useSkillsStore } from '@/stores/skills'
|
import { useSkillsStore } from '@/stores/skills'
|
||||||
|
import { skillsApi } from '@/api/skills'
|
||||||
import SkillCard from '@/components/skills/SkillCard.vue'
|
import SkillCard from '@/components/skills/SkillCard.vue'
|
||||||
import SkillDetail from '@/components/skills/SkillDetail.vue'
|
import SkillDetail from '@/components/skills/SkillDetail.vue'
|
||||||
|
|
||||||
|
|
@ -49,6 +77,14 @@ const skillsStore = useSkillsStore()
|
||||||
const selectedCapability = ref<string | undefined>(undefined)
|
const selectedCapability = ref<string | undefined>(undefined)
|
||||||
const showDetail = ref(false)
|
const showDetail = ref(false)
|
||||||
|
|
||||||
|
// Install state
|
||||||
|
const showInstallModal = ref(false)
|
||||||
|
const installName = ref('')
|
||||||
|
const installSource = ref('')
|
||||||
|
const installing = ref(false)
|
||||||
|
const installError = ref('')
|
||||||
|
const installSuccess = ref('')
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
skillsStore.fetchSkills(),
|
skillsStore.fetchSkills(),
|
||||||
|
|
@ -77,26 +113,50 @@ function handleDetailClose(): void {
|
||||||
showDetail.value = false
|
showDetail.value = false
|
||||||
skillsStore.clearSelectedSkill()
|
skillsStore.clearSelectedSkill()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleInstall(): Promise<void> {
|
||||||
|
if (!installName.value.trim()) {
|
||||||
|
installError.value = '请输入技能名称'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
installing.value = true
|
||||||
|
installError.value = ''
|
||||||
|
installSuccess.value = ''
|
||||||
|
try {
|
||||||
|
const result = await skillsApi.installSkill(
|
||||||
|
installName.value.trim(),
|
||||||
|
installSource.value.trim() || undefined
|
||||||
|
)
|
||||||
|
installSuccess.value = result.name
|
||||||
|
message.success(`技能 ${result.name} 安装成功!`)
|
||||||
|
// Refresh skill list
|
||||||
|
await handleRefresh()
|
||||||
|
} catch (e: any) {
|
||||||
|
installError.value = e?.message || '安装失败'
|
||||||
|
} finally {
|
||||||
|
installing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.skills-view {
|
.skills-view {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 16px 24px;
|
padding: var(--space-4) var(--space-6);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background: #fff;
|
background: var(--bg-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.skills-view__header {
|
.skills-view__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 16px;
|
margin-bottom: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.skills-view__grid {
|
.skills-view__grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
gap: 16px;
|
gap: var(--space-4);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue