style(format): 格式化代码
This commit is contained in:
parent
ba7c1299a5
commit
ee624513f1
@ -302,7 +302,9 @@ class LightRagKB(KnowledgeBase):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def index_file(self, db_id: str, file_id: str, operator_id: str | None = None, params: dict | None = None) -> dict:
|
async def index_file(
|
||||||
|
self, db_id: str, file_id: str, operator_id: str | None = None, params: dict | None = None
|
||||||
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Index parsed file (Status: INDEXING -> INDEXED/ERROR_INDEXING)
|
Index parsed file (Status: INDEXING -> INDEXED/ERROR_INDEXING)
|
||||||
|
|
||||||
@ -395,7 +397,7 @@ class LightRagKB(KnowledgeBase):
|
|||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Indexed file {file_id} into LightRAG with {len(chunks)} chunks, "
|
f"Indexed file {file_id} into LightRAG with {len(chunks)} chunks, "
|
||||||
f"chunk_preset_id={processing_params.get('chunk_preset_id')}"
|
f"chunk_preset_id={params.get('chunk_preset_id')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update status
|
# Update status
|
||||||
|
|||||||
@ -284,7 +284,9 @@ class MilvusKB(KnowledgeBase):
|
|||||||
"""将文本分割成块"""
|
"""将文本分割成块"""
|
||||||
return chunk_markdown(text, file_id, filename, params)
|
return chunk_markdown(text, file_id, filename, params)
|
||||||
|
|
||||||
async def index_file(self, db_id: str, file_id: str, operator_id: str | None = None, params: dict | None = None) -> dict:
|
async def index_file(
|
||||||
|
self, db_id: str, file_id: str, operator_id: str | None = None, params: dict | None = None
|
||||||
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Index parsed file (Status: INDEXING -> INDEXED/ERROR_INDEXING)
|
Index parsed file (Status: INDEXING -> INDEXED/ERROR_INDEXING)
|
||||||
|
|
||||||
|
|||||||
@ -415,7 +415,9 @@ class KnowledgeBaseManager:
|
|||||||
kb_instance = await self._get_kb_for_database(db_id)
|
kb_instance = await self._get_kb_for_database(db_id)
|
||||||
return await kb_instance.parse_file(db_id, file_id, operator_id)
|
return await kb_instance.parse_file(db_id, file_id, operator_id)
|
||||||
|
|
||||||
async def index_file(self, db_id: str, file_id: str, operator_id: str | None = None, params: dict | None = None) -> dict:
|
async def index_file(
|
||||||
|
self, db_id: str, file_id: str, operator_id: str | None = None, params: dict | None = None
|
||||||
|
) -> dict:
|
||||||
"""Index parsed file"""
|
"""Index parsed file"""
|
||||||
kb_instance = await self._get_kb_for_database(db_id)
|
kb_instance = await self._get_kb_for_database(db_id)
|
||||||
return await kb_instance.index_file(db_id, file_id, operator_id, params=params)
|
return await kb_instance.index_file(db_id, file_id, operator_id, params=params)
|
||||||
|
|||||||
@ -263,6 +263,7 @@ async def save_messages_from_langgraph_state(
|
|||||||
elif msg_type == "tool":
|
elif msg_type == "tool":
|
||||||
await _save_tool_message(conv_repo, msg_dict)
|
await _save_tool_message(conv_repo, msg_dict)
|
||||||
|
|
||||||
|
|
||||||
def _extract_interrupt_info(state) -> Any | None:
|
def _extract_interrupt_info(state) -> Any | None:
|
||||||
"""从 LangGraph state 中提取中断信息"""
|
"""从 LangGraph state 中提取中断信息"""
|
||||||
if hasattr(state, "tasks") and state.tasks:
|
if hasattr(state, "tasks") and state.tasks:
|
||||||
@ -1086,7 +1087,7 @@ async def stream_agent_resume(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error saving messages from LangGraph state: {e}")
|
logger.error(f"Error saving messages from LangGraph state: {e}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
yield make_chunk(status="warning", message=f"消息保存失败: {e}", meta=meta)
|
yield make_resume_chunk(status="warning", message=f"消息保存失败: {e}", meta=meta)
|
||||||
|
|
||||||
yield make_resume_chunk(status="finished", meta=meta)
|
yield make_resume_chunk(status="finished", meta=meta)
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ from typing import Any
|
|||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from yuxi.config.builtin_providers import BUILTIN_PROVIDERS
|
||||||
from yuxi.repositories.model_provider_repository import (
|
from yuxi.repositories.model_provider_repository import (
|
||||||
create_model_provider,
|
create_model_provider,
|
||||||
delete_model_provider,
|
delete_model_provider,
|
||||||
@ -14,7 +15,6 @@ from yuxi.repositories.model_provider_repository import (
|
|||||||
list_model_providers,
|
list_model_providers,
|
||||||
update_model_provider,
|
update_model_provider,
|
||||||
)
|
)
|
||||||
from yuxi.config.builtin_providers import BUILTIN_PROVIDERS
|
|
||||||
from yuxi.services.model_cache import is_v2_spec_format
|
from yuxi.services.model_cache import is_v2_spec_format
|
||||||
from yuxi.storage.postgres.models_business import ModelProvider
|
from yuxi.storage.postgres.models_business import ModelProvider
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,8 @@ export default defineConfig({
|
|||||||
ignoreDeadLinks: [
|
ignoreDeadLinks: [
|
||||||
/localhost/,
|
/localhost/,
|
||||||
/CONTRIBUTING$/,
|
/CONTRIBUTING$/,
|
||||||
/docker-compose\.yml$/
|
/docker-compose\.yml$/,
|
||||||
|
/^\.\/intro\//
|
||||||
],
|
],
|
||||||
markdown: {
|
markdown: {
|
||||||
config: (md) => {
|
config: (md) => {
|
||||||
|
|||||||
@ -27,7 +27,9 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
transition: color 0.15s, background 0.15s;
|
transition:
|
||||||
|
color 0.15s,
|
||||||
|
background 0.15s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--gray-700);
|
color: var(--gray-700);
|
||||||
@ -90,4 +92,4 @@
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -86,7 +86,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Provider 标签样式
|
// Provider 标签样式
|
||||||
.provider-tag {
|
.provider-tag {
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
|
|||||||
@ -101,7 +101,12 @@ const emit = defineEmits(['update:value', 'change'])
|
|||||||
|
|
||||||
const v2Models = ref({})
|
const v2Models = ref({})
|
||||||
const v1ModelStatuses = reactive({})
|
const v1ModelStatuses = reactive({})
|
||||||
const { statusMap: v2ModelStatuses, getStatusIcon: getV2StatusIcon, getStatusClass: getV2StatusClass, getStatusTooltip: getV2StatusTooltip, checkV2Status, checkV2Statuses } = useModelStatus()
|
const {
|
||||||
|
getStatusIcon: getV2StatusIcon,
|
||||||
|
getStatusClass: getV2StatusClass,
|
||||||
|
getStatusTooltip: getV2StatusTooltip,
|
||||||
|
checkV2Statuses
|
||||||
|
} = useModelStatus()
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
checkingStatus: false
|
checkingStatus: false
|
||||||
})
|
})
|
||||||
|
|||||||
@ -530,12 +530,33 @@ const deleteDatabase = () => {
|
|||||||
background: var(--gray-100);
|
background: var(--gray-100);
|
||||||
color: var(--gray-600);
|
color: var(--gray-600);
|
||||||
|
|
||||||
&.tag-purple { background: #f3e8ff; color: #7c3aed; }
|
&.tag-purple {
|
||||||
&.tag-blue { background: var(--color-info-50); color: var(--color-info-700); }
|
background: #f3e8ff;
|
||||||
&.tag-red { background: var(--color-error-50); color: var(--color-error-700); }
|
color: #7c3aed;
|
||||||
&.tag-green { background: var(--color-success-50); color: var(--color-success-700); }
|
}
|
||||||
&.tag-gold { background: #fef3c7; color: #d97706; }
|
&.tag-blue {
|
||||||
&.tag-cyan { background: #cffafe; color: #0891b2; }
|
background: var(--color-info-50);
|
||||||
&.tag-orange { background: #fff7ed; color: #ea580c; }
|
color: var(--color-info-700);
|
||||||
|
}
|
||||||
|
&.tag-red {
|
||||||
|
background: var(--color-error-50);
|
||||||
|
color: var(--color-error-700);
|
||||||
|
}
|
||||||
|
&.tag-green {
|
||||||
|
background: var(--color-success-50);
|
||||||
|
color: var(--color-success-700);
|
||||||
|
}
|
||||||
|
&.tag-gold {
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #d97706;
|
||||||
|
}
|
||||||
|
&.tag-cyan {
|
||||||
|
background: #cffafe;
|
||||||
|
color: #0891b2;
|
||||||
|
}
|
||||||
|
&.tag-orange {
|
||||||
|
background: #fff7ed;
|
||||||
|
color: #ea580c;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -125,7 +125,7 @@ const refreshCache = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
const { statusMap: modelStatusMap, getStatusIcon, getStatusTooltip } = useModelStatus()
|
useModelStatus()
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
currentModelStatus: null,
|
currentModelStatus: null,
|
||||||
checkingStatus: false,
|
checkingStatus: false,
|
||||||
@ -197,7 +197,6 @@ const handleSelectV2Model = (spec) => {
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import '@/assets/css/model-selector-common.less';
|
@import '@/assets/css/model-selector-common.less';
|
||||||
|
|
||||||
|
|
||||||
// 状态检查按钮
|
// 状态检查按钮
|
||||||
.status-check-button {
|
.status-check-button {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@ -55,11 +55,7 @@
|
|||||||
@click="selectTool(tool)"
|
@click="selectTool(tool)"
|
||||||
>
|
>
|
||||||
<div class="item-header">
|
<div class="item-header">
|
||||||
<component
|
<component :is="getToolIcon(tool.id) || Wrench" :size="16" class="item-icon" />
|
||||||
:is="getToolIcon(tool.id) || Wrench"
|
|
||||||
:size="16"
|
|
||||||
class="item-icon"
|
|
||||||
/>
|
|
||||||
<span class="item-name">{{ tool.name }}</span>
|
<span class="item-name">{{ tool.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-details item-details-inline">
|
<div class="item-details item-details-inline">
|
||||||
|
|||||||
@ -12,7 +12,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="extension-card-info">
|
<div class="extension-card-info">
|
||||||
<span class="extension-card-name" :title="title">{{ title }}</span>
|
<span class="extension-card-name" :title="title">{{ title }}</span>
|
||||||
<span v-if="subtitle" class="extension-card-subtitle" :title="subtitle">{{ subtitle }}</span>
|
<span v-if="subtitle" class="extension-card-subtitle" :title="subtitle">{{
|
||||||
|
subtitle
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="extension-card-status">
|
<div class="extension-card-status">
|
||||||
<slot name="status" />
|
<slot name="status" />
|
||||||
@ -23,17 +25,17 @@
|
|||||||
class="card-action-btn"
|
class="card-action-btn"
|
||||||
:class="`card-action-btn--${actionVariant || 'primary'}`"
|
:class="`card-action-btn--${actionVariant || 'primary'}`"
|
||||||
@click.stop="$emit('actionClick')"
|
@click.stop="$emit('actionClick')"
|
||||||
>{{ actionLabel }}</button>
|
>
|
||||||
|
{{ actionLabel }}
|
||||||
|
</button>
|
||||||
<template v-else-if="status">
|
<template v-else-if="status">
|
||||||
<span
|
<span
|
||||||
v-if="status.label"
|
v-if="status.label"
|
||||||
class="card-status-tag"
|
class="card-status-tag"
|
||||||
:class="`card-status-tag--${status.level || 'info'}`"
|
:class="`card-status-tag--${status.level || 'info'}`"
|
||||||
>{{ status.label }}</span>
|
>{{ status.label }}</span
|
||||||
<span
|
>
|
||||||
class="card-status-dot"
|
<span class="card-status-dot" :class="`card-status-dot--${statusDotColor}`"></span>
|
||||||
:class="`card-status-dot--${statusDotColor}`"
|
|
||||||
></span>
|
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -52,7 +54,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="$slots.tags || (normalizedTags && normalizedTags.length > 0)" class="extension-card-tags">
|
<div
|
||||||
|
v-if="$slots.tags || (normalizedTags && normalizedTags.length > 0)"
|
||||||
|
class="extension-card-tags"
|
||||||
|
>
|
||||||
<slot name="tags">
|
<slot name="tags">
|
||||||
<span
|
<span
|
||||||
v-for="(tag, idx) in normalizedTags"
|
v-for="(tag, idx) in normalizedTags"
|
||||||
@ -60,7 +65,8 @@
|
|||||||
class="card-tag"
|
class="card-tag"
|
||||||
:class="tag.color ? `tag-${tag.color}` : ''"
|
:class="tag.color ? `tag-${tag.color}` : ''"
|
||||||
:style="tag.bgColor ? { backgroundColor: tag.bgColor } : {}"
|
:style="tag.bgColor ? { backgroundColor: tag.bgColor } : {}"
|
||||||
>{{ tag.name }}</span>
|
>{{ tag.name }}</span
|
||||||
|
>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -116,7 +122,9 @@ const normalizedTags = computed(() => {
|
|||||||
border: 1px solid var(--gray-150);
|
border: 1px solid var(--gray-150);
|
||||||
background: linear-gradient(45deg, var(--gray-0) 0%, var(--gray-25) 100%);
|
background: linear-gradient(45deg, var(--gray-0) 0%, var(--gray-25) 100%);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
transition:
|
||||||
|
border-color 0.2s ease,
|
||||||
|
box-shadow 0.2s ease;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -206,7 +214,6 @@ const normalizedTags = computed(() => {
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.info-row {
|
.info-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -260,13 +267,34 @@ const normalizedTags = computed(() => {
|
|||||||
background: var(--gray-100);
|
background: var(--gray-100);
|
||||||
color: var(--gray-600);
|
color: var(--gray-600);
|
||||||
|
|
||||||
&.tag-purple { background: #f3e8ff; color: #7c3aed; }
|
&.tag-purple {
|
||||||
&.tag-blue { background: var(--color-info-50); color: var(--color-info-700); }
|
background: #f3e8ff;
|
||||||
&.tag-red { background: var(--color-error-50); color: var(--color-error-700); }
|
color: #7c3aed;
|
||||||
&.tag-green { background: var(--color-success-50); color: var(--color-success-700); }
|
}
|
||||||
&.tag-gold { background: #fef3c7; color: #d97706; }
|
&.tag-blue {
|
||||||
&.tag-cyan { background: #cffafe; color: #0891b2; }
|
background: var(--color-info-50);
|
||||||
&.tag-orange { background: #fff7ed; color: #ea580c; }
|
color: var(--color-info-700);
|
||||||
|
}
|
||||||
|
&.tag-red {
|
||||||
|
background: var(--color-error-50);
|
||||||
|
color: var(--color-error-700);
|
||||||
|
}
|
||||||
|
&.tag-green {
|
||||||
|
background: var(--color-success-50);
|
||||||
|
color: var(--color-success-700);
|
||||||
|
}
|
||||||
|
&.tag-gold {
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #d97706;
|
||||||
|
}
|
||||||
|
&.tag-cyan {
|
||||||
|
background: #cffafe;
|
||||||
|
color: #0891b2;
|
||||||
|
}
|
||||||
|
&.tag-orange {
|
||||||
|
background: #fff7ed;
|
||||||
|
color: #ea580c;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-action-btn {
|
.card-action-btn {
|
||||||
@ -282,13 +310,17 @@ const normalizedTags = computed(() => {
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease;
|
transition:
|
||||||
|
background-color 0.18s ease,
|
||||||
|
border-color 0.18s ease,
|
||||||
|
color 0.18s ease;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
background: var(--main-50);
|
background: var(--main-50);
|
||||||
color: var(--main-700);
|
color: var(--main-700);
|
||||||
|
|
||||||
&:hover, &:focus {
|
&:hover,
|
||||||
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: var(--main-200);
|
border-color: var(--main-200);
|
||||||
background: var(--main-50);
|
background: var(--main-50);
|
||||||
@ -299,7 +331,8 @@ const normalizedTags = computed(() => {
|
|||||||
background: var(--color-error-50);
|
background: var(--color-error-50);
|
||||||
color: var(--color-error-700);
|
color: var(--color-error-700);
|
||||||
|
|
||||||
&:hover, &:focus {
|
&:hover,
|
||||||
|
&:focus {
|
||||||
border-color: var(--color-error-200);
|
border-color: var(--color-error-200);
|
||||||
background: var(--color-error-50);
|
background: var(--color-error-50);
|
||||||
color: var(--color-error-800);
|
color: var(--color-error-800);
|
||||||
@ -319,7 +352,8 @@ const normalizedTags = computed(() => {
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
||||||
&--success, &--on {
|
&--success,
|
||||||
|
&--on {
|
||||||
background: var(--color-success-50);
|
background: var(--color-success-50);
|
||||||
color: var(--color-success-700);
|
color: var(--color-success-700);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="extension-card-grid" :style="{ gridTemplateColumns: `repeat(auto-fill, minmax(${minWidth}px, 1fr))` }">
|
<div
|
||||||
|
class="extension-card-grid"
|
||||||
|
:style="{ gridTemplateColumns: `repeat(auto-fill, minmax(${minWidth}px, 1fr))` }"
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
<div v-if="!$slots.default && items.length === 0" class="extension-card-grid-empty">
|
<div v-if="!$slots.default && items.length === 0" class="extension-card-grid-empty">
|
||||||
<a-empty :image="false" :description="emptyText" />
|
<a-empty :image="false" :description="emptyText" />
|
||||||
|
|||||||
@ -28,4 +28,4 @@ defineProps({
|
|||||||
max-width: 720px;
|
max-width: 720px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -76,4 +76,4 @@ defineProps({
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -14,8 +14,14 @@
|
|||||||
</template>
|
</template>
|
||||||
</PageShoulder>
|
</PageShoulder>
|
||||||
|
|
||||||
<div v-if="filteredEnabledServers.length === 0 && filteredDisabledServers.length === 0" class="extension-card-grid-empty-state">
|
<div
|
||||||
<a-empty :image="false" :description="searchQuery ? '无匹配 MCP' : '暂无 MCP,点击上方按钮添加'" />
|
v-if="filteredEnabledServers.length === 0 && filteredDisabledServers.length === 0"
|
||||||
|
class="extension-card-grid-empty-state"
|
||||||
|
>
|
||||||
|
<a-empty
|
||||||
|
:image="false"
|
||||||
|
:description="searchQuery ? '无匹配 MCP' : '暂无 MCP,点击上方按钮添加'"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@ -89,7 +95,10 @@ const formModalVisible = ref(false)
|
|||||||
|
|
||||||
const filteredServers = computed(() => {
|
const filteredServers = computed(() => {
|
||||||
const sorted = [...servers.value].sort((a, b) =>
|
const sorted = [...servers.value].sort((a, b) =>
|
||||||
String(a.name || '').localeCompare(String(b.name || ''), 'zh-Hans-CN', { sensitivity: 'base', numeric: true })
|
String(a.name || '').localeCompare(String(b.name || ''), 'zh-Hans-CN', {
|
||||||
|
sensitivity: 'base',
|
||||||
|
numeric: true
|
||||||
|
})
|
||||||
)
|
)
|
||||||
if (!searchQuery.value) return sorted
|
if (!searchQuery.value) return sorted
|
||||||
const q = searchQuery.value.toLowerCase()
|
const q = searchQuery.value.toLowerCase()
|
||||||
@ -98,8 +107,12 @@ const filteredServers = computed(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const filteredEnabledServers = computed(() => filteredServers.value.filter((item) => !!item.enabled))
|
const filteredEnabledServers = computed(() =>
|
||||||
const filteredDisabledServers = computed(() => filteredServers.value.filter((item) => !item.enabled))
|
filteredServers.value.filter((item) => !!item.enabled)
|
||||||
|
)
|
||||||
|
const filteredDisabledServers = computed(() =>
|
||||||
|
filteredServers.value.filter((item) => !item.enabled)
|
||||||
|
)
|
||||||
|
|
||||||
const getTransportColor = (transport) => {
|
const getTransportColor = (transport) => {
|
||||||
const colors = { sse: 'orange', stdio: 'green', streamable_http: 'blue' }
|
const colors = { sse: 'orange', stdio: 'green', streamable_http: 'blue' }
|
||||||
@ -109,7 +122,8 @@ const getTransportColor = (transport) => {
|
|||||||
const mcpTags = (server) => {
|
const mcpTags = (server) => {
|
||||||
const tags = []
|
const tags = []
|
||||||
if (server.created_by === 'system') tags.push({ name: '内置' })
|
if (server.created_by === 'system') tags.push({ name: '内置' })
|
||||||
if (server.transport) tags.push({ name: server.transport, color: getTransportColor(server.transport) })
|
if (server.transport)
|
||||||
|
tags.push({ name: server.transport, color: getTransportColor(server.transport) })
|
||||||
return tags
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,4 +182,4 @@ defineExpose({ fetchServers, loading })
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -40,7 +40,9 @@
|
|||||||
:class="[
|
:class="[
|
||||||
'lucide-icon-btn',
|
'lucide-icon-btn',
|
||||||
'extension-panel-action',
|
'extension-panel-action',
|
||||||
server?.enabled === false ? 'extension-panel-action-primary' : 'extension-panel-action-danger'
|
server?.enabled === false
|
||||||
|
? 'extension-panel-action-primary'
|
||||||
|
: 'extension-panel-action-danger'
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<Plus v-if="server?.enabled === false" :size="14" />
|
<Plus v-if="server?.enabled === false" :size="14" />
|
||||||
@ -68,21 +70,31 @@
|
|||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<label>传输类型</label>
|
<label>传输类型</label>
|
||||||
<span>
|
<span>
|
||||||
<a-tag :color="getTransportColor(server.transport)">{{ server.transport }}</a-tag>
|
<a-tag :color="getTransportColor(server.transport)">{{
|
||||||
|
server.transport
|
||||||
|
}}</a-tag>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item" v-if="Array.isArray(server.tags) && server.tags.length > 0">
|
<div
|
||||||
|
class="info-item"
|
||||||
|
v-if="Array.isArray(server.tags) && server.tags.length > 0"
|
||||||
|
>
|
||||||
<label>标签</label>
|
<label>标签</label>
|
||||||
<span>
|
<span>
|
||||||
<a-tag v-for="tag in server.tags" :key="tag">{{ tag }}</a-tag>
|
<a-tag v-for="tag in server.tags" :key="tag">{{ tag }}</a-tag>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="server.transport === 'streamable_http' || server.transport === 'sse'">
|
<template
|
||||||
|
v-if="server.transport === 'streamable_http' || server.transport === 'sse'"
|
||||||
|
>
|
||||||
<div class="info-item" v-if="server.url">
|
<div class="info-item" v-if="server.url">
|
||||||
<label>MCP URL</label>
|
<label>MCP URL</label>
|
||||||
<span class="code-inline text-break-all">{{ server.url }}</span>
|
<span class="code-inline text-break-all">{{ server.url }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item" v-if="server.headers && Object.keys(server.headers).length > 0">
|
<div
|
||||||
|
class="info-item"
|
||||||
|
v-if="server.headers && Object.keys(server.headers).length > 0"
|
||||||
|
>
|
||||||
<label>请求头</label>
|
<label>请求头</label>
|
||||||
<pre class="code-pre">{{ JSON.stringify(server.headers, null, 2) }}</pre>
|
<pre class="code-pre">{{ JSON.stringify(server.headers, null, 2) }}</pre>
|
||||||
</div>
|
</div>
|
||||||
@ -103,7 +115,9 @@
|
|||||||
<div class="info-item" v-if="server.args && server.args.length > 0">
|
<div class="info-item" v-if="server.args && server.args.length > 0">
|
||||||
<label>参数</label>
|
<label>参数</label>
|
||||||
<span>
|
<span>
|
||||||
<a-tag v-for="(arg, index) in server.args" :key="index" size="small">{{ arg }}</a-tag>
|
<a-tag v-for="(arg, index) in server.args" :key="index" size="small">{{
|
||||||
|
arg
|
||||||
|
}}</a-tag>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item" v-if="server.env && Object.keys(server.env).length > 0">
|
<div class="info-item" v-if="server.env && Object.keys(server.env).length > 0">
|
||||||
@ -184,7 +198,10 @@
|
|||||||
<div class="tool-description" v-if="tool.description">
|
<div class="tool-description" v-if="tool.description">
|
||||||
{{ tool.description }}
|
{{ tool.description }}
|
||||||
</div>
|
</div>
|
||||||
<a-collapse v-if="tool.parameters && Object.keys(tool.parameters).length > 0" ghost>
|
<a-collapse
|
||||||
|
v-if="tool.parameters && Object.keys(tool.parameters).length > 0"
|
||||||
|
ghost
|
||||||
|
>
|
||||||
<a-collapse-panel key="params" header="参数">
|
<a-collapse-panel key="params" header="参数">
|
||||||
<div class="params-list">
|
<div class="params-list">
|
||||||
<div
|
<div
|
||||||
@ -194,10 +211,16 @@
|
|||||||
>
|
>
|
||||||
<div class="param-header">
|
<div class="param-header">
|
||||||
<span class="param-name">{{ paramName }}</span>
|
<span class="param-name">{{ paramName }}</span>
|
||||||
<span class="param-required" v-if="tool.required?.includes(paramName)">必填</span>
|
<span
|
||||||
|
class="param-required"
|
||||||
|
v-if="tool.required?.includes(paramName)"
|
||||||
|
>必填</span
|
||||||
|
>
|
||||||
<span class="param-type">{{ param.type || 'any' }}</span>
|
<span class="param-type">{{ param.type || 'any' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="param-desc" v-if="param.description">{{ param.description }}</div>
|
<div class="param-desc" v-if="param.description">
|
||||||
|
{{ param.description }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
@ -272,7 +295,9 @@ const filteredTools = computed(() => {
|
|||||||
if (!toolSearchText.value) return tools.value
|
if (!toolSearchText.value) return tools.value
|
||||||
const search = toolSearchText.value.toLowerCase()
|
const search = toolSearchText.value.toLowerCase()
|
||||||
return tools.value.filter(
|
return tools.value.filter(
|
||||||
(t) => t.name.toLowerCase().includes(search) || (t.description && t.description.toLowerCase().includes(search))
|
(t) =>
|
||||||
|
t.name.toLowerCase().includes(search) ||
|
||||||
|
(t.description && t.description.toLowerCase().includes(search))
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -458,4 +483,4 @@ onMounted(() => {
|
|||||||
padding: 16px var(--page-padding);
|
padding: 16px var(--page-padding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -18,7 +18,11 @@
|
|||||||
|
|
||||||
<a-form v-if="formMode === 'form'" layout="vertical" class="extension-form">
|
<a-form v-if="formMode === 'form'" layout="vertical" class="extension-form">
|
||||||
<a-form-item label="MCP 名称" required class="form-item">
|
<a-form-item label="MCP 名称" required class="form-item">
|
||||||
<a-input v-model:value="form.name" placeholder="请输入 MCP 名称(唯一标识)" :disabled="editMode" />
|
<a-input
|
||||||
|
v-model:value="form.name"
|
||||||
|
placeholder="请输入 MCP 名称(唯一标识)"
|
||||||
|
:disabled="editMode"
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="描述" class="form-item">
|
<a-form-item label="描述" class="form-item">
|
||||||
<a-input v-model:value="form.description" placeholder="请输入 MCP 描述" />
|
<a-input v-model:value="form.description" placeholder="请输入 MCP 描述" />
|
||||||
@ -44,17 +48,31 @@
|
|||||||
<a-input v-model:value="form.url" placeholder="https://example.com/mcp" />
|
<a-input v-model:value="form.url" placeholder="https://example.com/mcp" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="HTTP 请求头" class="form-item">
|
<a-form-item label="HTTP 请求头" class="form-item">
|
||||||
<a-textarea v-model:value="form.headersText" placeholder='JSON 格式,如:{"Authorization": "Bearer xxx"}' :rows="3" />
|
<a-textarea
|
||||||
|
v-model:value="form.headersText"
|
||||||
|
placeholder='JSON 格式,如:{"Authorization": "Bearer xxx"}'
|
||||||
|
:rows="3"
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="HTTP 超时(秒)" class="form-item">
|
<a-form-item label="HTTP 超时(秒)" class="form-item">
|
||||||
<a-input-number v-model:value="form.timeout" :min="1" :max="300" style="width: 100%" />
|
<a-input-number
|
||||||
|
v-model:value="form.timeout"
|
||||||
|
:min="1"
|
||||||
|
:max="300"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="SSE 读取超时(秒)" class="form-item">
|
<a-form-item label="SSE 读取超时(秒)" class="form-item">
|
||||||
<a-input-number v-model:value="form.sse_read_timeout" :min="1" :max="300" style="width: 100%" />
|
<a-input-number
|
||||||
|
v-model:value="form.sse_read_timeout"
|
||||||
|
:min="1"
|
||||||
|
:max="300"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
@ -64,14 +82,24 @@
|
|||||||
<a-input v-model:value="form.command" placeholder="例如:npx 或 /path/to/server" />
|
<a-input v-model:value="form.command" placeholder="例如:npx 或 /path/to/server" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="参数" class="form-item">
|
<a-form-item label="参数" class="form-item">
|
||||||
<a-select v-model:value="form.args" mode="tags" placeholder="输入参数后回车添加,如:-m" style="width: 100%" />
|
<a-select
|
||||||
|
v-model:value="form.args"
|
||||||
|
mode="tags"
|
||||||
|
placeholder="输入参数后回车添加,如:-m"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="环境变量" class="form-item">
|
<a-form-item label="环境变量" class="form-item">
|
||||||
<McpEnvEditor v-model="form.env" />
|
<McpEnvEditor v-model="form.env" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<a-form-item label="标签" class="form-item">
|
<a-form-item label="标签" class="form-item">
|
||||||
<a-select v-model:value="form.tags" mode="tags" placeholder="输入标签后回车添加" style="width: 100%" />
|
<a-select
|
||||||
|
v-model:value="form.tags"
|
||||||
|
mode="tags"
|
||||||
|
placeholder="输入标签后回车添加"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
@ -79,7 +107,7 @@
|
|||||||
<a-textarea
|
<a-textarea
|
||||||
v-model:value="jsonContent"
|
v-model:value="jsonContent"
|
||||||
:rows="15"
|
:rows="15"
|
||||||
placeholder='请输入 JSON 配置'
|
placeholder="请输入 JSON 配置"
|
||||||
class="json-textarea"
|
class="json-textarea"
|
||||||
/>
|
/>
|
||||||
<div class="json-actions">
|
<div class="json-actions">
|
||||||
@ -129,7 +157,10 @@ const form = reactive({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const isStdioTransport = computed(
|
const isStdioTransport = computed(
|
||||||
() => String(form.transport || '').trim().toLowerCase() === 'stdio'
|
() =>
|
||||||
|
String(form.transport || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase() === 'stdio'
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@ -310,4 +341,4 @@ const handleFormSubmit = async () => {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -2,7 +2,11 @@
|
|||||||
<div class="skill-cards-page extension-page-root">
|
<div class="skill-cards-page extension-page-root">
|
||||||
<PageShoulder search-placeholder="搜索技能..." v-model:search="searchQuery">
|
<PageShoulder search-placeholder="搜索技能..." v-model:search="searchQuery">
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<a-button @click="handleOpenRemoteInstall" :disabled="loading || importing" class="lucide-icon-btn">
|
<a-button
|
||||||
|
@click="handleOpenRemoteInstall"
|
||||||
|
:disabled="loading || importing"
|
||||||
|
class="lucide-icon-btn"
|
||||||
|
>
|
||||||
<Computer :size="14" />
|
<Computer :size="14" />
|
||||||
<span>远程安装</span>
|
<span>远程安装</span>
|
||||||
</a-button>
|
</a-button>
|
||||||
@ -26,12 +30,17 @@
|
|||||||
</template>
|
</template>
|
||||||
</PageShoulder>
|
</PageShoulder>
|
||||||
|
|
||||||
<div v-if="filteredInstalledSkills.length === 0 && filteredUninstalledBuiltinSkills.length === 0" class="extension-card-grid-empty-state">
|
<div
|
||||||
|
v-if="filteredInstalledSkills.length === 0 && filteredUninstalledBuiltinSkills.length === 0"
|
||||||
|
class="extension-card-grid-empty-state"
|
||||||
|
>
|
||||||
<a-empty :image="false" description="无匹配技能" />
|
<a-empty :image="false" description="无匹配技能" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div v-if="filteredInstalledSkills.length" class="extension-section-header">已添加 Skills</div>
|
<div v-if="filteredInstalledSkills.length" class="extension-section-header">
|
||||||
|
已添加 Skills
|
||||||
|
</div>
|
||||||
<ExtensionCardGrid>
|
<ExtensionCardGrid>
|
||||||
<ExtensionCard
|
<ExtensionCard
|
||||||
v-for="skill in filteredInstalledSkills"
|
v-for="skill in filteredInstalledSkills"
|
||||||
@ -46,7 +55,9 @@
|
|||||||
</ExtensionCard>
|
</ExtensionCard>
|
||||||
</ExtensionCardGrid>
|
</ExtensionCardGrid>
|
||||||
|
|
||||||
<div v-if="filteredUninstalledBuiltinSkills.length" class="extension-section-header">可添加 Skills</div>
|
<div v-if="filteredUninstalledBuiltinSkills.length" class="extension-section-header">
|
||||||
|
可添加 Skills
|
||||||
|
</div>
|
||||||
<ExtensionCardGrid v-if="filteredUninstalledBuiltinSkills.length">
|
<ExtensionCardGrid v-if="filteredUninstalledBuiltinSkills.length">
|
||||||
<ExtensionCard
|
<ExtensionCard
|
||||||
v-for="skill in filteredUninstalledBuiltinSkills"
|
v-for="skill in filteredUninstalledBuiltinSkills"
|
||||||
@ -102,13 +113,24 @@
|
|||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<div class="remote-install-actions">
|
<div class="remote-install-actions">
|
||||||
<a-button :loading="listingRemoteSkills" :disabled="installingRemoteSkill" @click="handleListRemoteSkills">
|
<a-button
|
||||||
|
:loading="listingRemoteSkills"
|
||||||
|
:disabled="installingRemoteSkill"
|
||||||
|
@click="handleListRemoteSkills"
|
||||||
|
>
|
||||||
查看可安装 Skills
|
查看可安装 Skills
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="primary" :loading="installingRemoteSkill" :disabled="listingRemoteSkills" @click="handleInstallRemoteSkill">
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:loading="installingRemoteSkill"
|
||||||
|
:disabled="listingRemoteSkills"
|
||||||
|
@click="handleInstallRemoteSkill"
|
||||||
|
>
|
||||||
安装
|
安装
|
||||||
</a-button>
|
</a-button>
|
||||||
<span v-if="remoteInstallStatusText" class="remote-install-status">{{ remoteInstallStatusText }}</span>
|
<span v-if="remoteInstallStatusText" class="remote-install-status">{{
|
||||||
|
remoteInstallStatusText
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="remoteSkillOptions.length" class="remote-skill-summary">
|
<div v-if="remoteSkillOptions.length" class="remote-skill-summary">
|
||||||
共发现 {{ remoteSkillOptions.length }} 个 skills,可按输入内容筛选候选项。
|
共发现 {{ remoteSkillOptions.length }} 个 skills,可按输入内容筛选候选项。
|
||||||
@ -174,9 +196,10 @@ const installedSkillCards = computed(() => {
|
|||||||
...skill,
|
...skill,
|
||||||
sourceType: 'builtin',
|
sourceType: 'builtin',
|
||||||
sourceLabel: '内置',
|
sourceLabel: '内置',
|
||||||
status: skill.status === 'update_available'
|
status:
|
||||||
? { label: '更新可用', level: 'warning' }
|
skill.status === 'update_available'
|
||||||
: { label: '已安装', level: 'success' }
|
? { label: '更新可用', level: 'warning' }
|
||||||
|
: { label: '已安装', level: 'success' }
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
@ -194,7 +217,9 @@ const installedSkillCards = computed(() => {
|
|||||||
const filteredInstalledSkills = computed(() => installedSkillCards.value.filter(matchesSearch))
|
const filteredInstalledSkills = computed(() => installedSkillCards.value.filter(matchesSearch))
|
||||||
|
|
||||||
const filteredUninstalledBuiltinSkills = computed(() => {
|
const filteredUninstalledBuiltinSkills = computed(() => {
|
||||||
return (builtinSkills.value || []).filter((skill) => skill.status === 'not_installed' && matchesSearch(skill))
|
return (builtinSkills.value || []).filter(
|
||||||
|
(skill) => skill.status === 'not_installed' && matchesSearch(skill)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const filteredRemoteSkillOptions = computed(() =>
|
const filteredRemoteSkillOptions = computed(() =>
|
||||||
@ -208,7 +233,8 @@ const remoteInstallStatusText = computed(() => {
|
|||||||
if (!remoteInstallProgress.visible || !remoteInstallProgress.total) return ''
|
if (!remoteInstallProgress.visible || !remoteInstallProgress.total) return ''
|
||||||
const progressText = `[${remoteInstallProgress.completed}/${remoteInstallProgress.total}]`
|
const progressText = `[${remoteInstallProgress.completed}/${remoteInstallProgress.total}]`
|
||||||
const currentSkill = remoteInstallProgress.currentSkill || ''
|
const currentSkill = remoteInstallProgress.currentSkill || ''
|
||||||
const failedText = remoteInstallProgress.failed > 0 ? `, ${remoteInstallProgress.failed} failed` : ''
|
const failedText =
|
||||||
|
remoteInstallProgress.failed > 0 ? `, ${remoteInstallProgress.failed} failed` : ''
|
||||||
return `${progressText} ${currentSkill}${failedText}`.trim()
|
return `${progressText} ${currentSkill}${failedText}`.trim()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -401,12 +427,16 @@ onMounted(() => {
|
|||||||
fetchSkills()
|
fetchSkills()
|
||||||
})
|
})
|
||||||
|
|
||||||
defineExpose({ fetchSkills, handleImportUpload, openRemoteInstallModal: handleOpenRemoteInstall, loading })
|
defineExpose({
|
||||||
|
fetchSkills,
|
||||||
|
handleImportUpload,
|
||||||
|
openRemoteInstallModal: handleOpenRemoteInstall,
|
||||||
|
loading
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import '@/assets/css/extensions.less';
|
@import '@/assets/css/extensions.less';
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@ -469,4 +499,4 @@ defineExpose({ fetchSkills, handleImportUpload, openRemoteInstallModal: handleOp
|
|||||||
color: var(--gray-500);
|
color: var(--gray-500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -19,7 +19,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="detail-actions">
|
<div class="detail-actions">
|
||||||
<a-space :size="8">
|
<a-space :size="8">
|
||||||
<span v-if="currentSkillStatusLabel" class="panel-status-chip" :class="{ warning: currentSkillStatusTone === 'warning' }">
|
<span
|
||||||
|
v-if="currentSkillStatusLabel"
|
||||||
|
class="panel-status-chip"
|
||||||
|
:class="{ warning: currentSkillStatusTone === 'warning' }"
|
||||||
|
>
|
||||||
{{ currentSkillStatusLabel }}
|
{{ currentSkillStatusLabel }}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
@ -66,7 +70,9 @@
|
|||||||
<div v-if="!isInstalledSkill" class="builtin-uninstalled-state">
|
<div v-if="!isInstalledSkill" class="builtin-uninstalled-state">
|
||||||
<h3>{{ currentSkill.description }}</h3>
|
<h3>{{ currentSkill.description }}</h3>
|
||||||
<p>版本 {{ currentSkill.version }}</p>
|
<p>版本 {{ currentSkill.version }}</p>
|
||||||
<a-button type="primary" @click="handleInstallBuiltin(currentSkill)">安装内置 Skill</a-button>
|
<a-button type="primary" @click="handleInstallBuiltin(currentSkill)"
|
||||||
|
>安装内置 Skill</a-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a-tabs v-else v-model:activeKey="activeTab" class="minimal-tabs">
|
<a-tabs v-else v-model:activeKey="activeTab" class="minimal-tabs">
|
||||||
@ -79,9 +85,15 @@
|
|||||||
<div class="tree-header">
|
<div class="tree-header">
|
||||||
<span class="label">项目结构</span>
|
<span class="label">项目结构</span>
|
||||||
<div class="tree-actions">
|
<div class="tree-actions">
|
||||||
<a-tooltip v-if="!isBuiltinInstalledSkill" title="新建文件"><button @click="openCreateModal(false)"><FilePlus :size="14" /></button></a-tooltip>
|
<a-tooltip v-if="!isBuiltinInstalledSkill" title="新建文件"
|
||||||
<a-tooltip v-if="!isBuiltinInstalledSkill" title="新建目录"><button @click="openCreateModal(true)"><FolderPlus :size="14" /></button></a-tooltip>
|
><button @click="openCreateModal(false)"><FilePlus :size="14" /></button
|
||||||
<a-tooltip title="刷新"><button @click="reloadTree"><RotateCw :size="14" /></button></a-tooltip>
|
></a-tooltip>
|
||||||
|
<a-tooltip v-if="!isBuiltinInstalledSkill" title="新建目录"
|
||||||
|
><button @click="openCreateModal(true)"><FolderPlus :size="14" /></button
|
||||||
|
></a-tooltip>
|
||||||
|
<a-tooltip title="刷新"
|
||||||
|
><button @click="reloadTree"><RotateCw :size="14" /></button
|
||||||
|
></a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tree-content">
|
<div class="tree-content">
|
||||||
@ -127,7 +139,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-main">
|
<div class="editor-main">
|
||||||
<a-empty v-if="!selectedPath || selectedIsDir" description="选择文件以开始编辑" class="mt-40" />
|
<a-empty
|
||||||
|
v-if="!selectedPath || selectedIsDir"
|
||||||
|
description="选择文件以开始编辑"
|
||||||
|
class="mt-40"
|
||||||
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<MdPreview
|
<MdPreview
|
||||||
v-if="viewMode === 'preview'"
|
v-if="viewMode === 'preview'"
|
||||||
@ -172,13 +188,34 @@
|
|||||||
<div class="config-form">
|
<div class="config-form">
|
||||||
<a-form layout="vertical">
|
<a-form layout="vertical">
|
||||||
<a-form-item label="工具依赖 (Tools)">
|
<a-form-item label="工具依赖 (Tools)">
|
||||||
<a-select v-model:value="dependencyForm.tool_dependencies" mode="multiple" :options="toolDependencyOptions" placeholder="选择工具..." allow-clear show-search />
|
<a-select
|
||||||
|
v-model:value="dependencyForm.tool_dependencies"
|
||||||
|
mode="multiple"
|
||||||
|
:options="toolDependencyOptions"
|
||||||
|
placeholder="选择工具..."
|
||||||
|
allow-clear
|
||||||
|
show-search
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="MCP 依赖 (Model Context Protocol)">
|
<a-form-item label="MCP 依赖 (Model Context Protocol)">
|
||||||
<a-select v-model:value="dependencyForm.mcp_dependencies" mode="multiple" :options="mcpDependencyOptions" placeholder="选择 MCP 服务..." allow-clear show-search />
|
<a-select
|
||||||
|
v-model:value="dependencyForm.mcp_dependencies"
|
||||||
|
mode="multiple"
|
||||||
|
:options="mcpDependencyOptions"
|
||||||
|
placeholder="选择 MCP 服务..."
|
||||||
|
allow-clear
|
||||||
|
show-search
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Skill 依赖">
|
<a-form-item label="Skill 依赖">
|
||||||
<a-select v-model:value="dependencyForm.skill_dependencies" mode="multiple" :options="skillDependencyOptions" placeholder="选择 Skill..." allow-clear show-search />
|
<a-select
|
||||||
|
v-model:value="dependencyForm.skill_dependencies"
|
||||||
|
mode="multiple"
|
||||||
|
:options="skillDependencyOptions"
|
||||||
|
placeholder="选择 Skill..."
|
||||||
|
allow-clear
|
||||||
|
show-search
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
@ -270,11 +307,17 @@ const dependencyForm = reactive({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const isInstalledSkill = computed(() => {
|
const isInstalledSkill = computed(() => {
|
||||||
return !!(currentSkill.value && (currentSkill.value.installed_record || currentSkill.value.dir_path))
|
return !!(
|
||||||
|
currentSkill.value &&
|
||||||
|
(currentSkill.value.installed_record || currentSkill.value.dir_path)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const isBuiltinInstalledSkill = computed(() => {
|
const isBuiltinInstalledSkill = computed(() => {
|
||||||
return !!(isInstalledSkill.value && (currentSkill.value?.is_builtin || currentSkill.value?.installed_record))
|
return !!(
|
||||||
|
isInstalledSkill.value &&
|
||||||
|
(currentSkill.value?.is_builtin || currentSkill.value?.installed_record)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentSkillStatusLabel = computed(() => {
|
const currentSkillStatusLabel = computed(() => {
|
||||||
@ -312,7 +355,9 @@ const mcpDependencyOptions = computed(() =>
|
|||||||
(dependencyOptions.mcps || []).map((i) => ({ label: i, value: i }))
|
(dependencyOptions.mcps || []).map((i) => ({ label: i, value: i }))
|
||||||
)
|
)
|
||||||
const skillDependencyOptions = computed(() =>
|
const skillDependencyOptions = computed(() =>
|
||||||
(dependencyOptions.skills || []).filter((s) => s !== currentSkill.value?.slug).map((i) => ({ label: i, value: i }))
|
(dependencyOptions.skills || [])
|
||||||
|
.filter((s) => s !== currentSkill.value?.slug)
|
||||||
|
.map((i) => ({ label: i, value: i }))
|
||||||
)
|
)
|
||||||
|
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
@ -451,7 +496,13 @@ const handleTreeSelect = async (keys, info) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const saveCurrentFile = async () => {
|
const saveCurrentFile = async () => {
|
||||||
if (!currentSkill.value || !selectedPath.value || selectedIsDir.value || isBuiltinInstalledSkill.value) return
|
if (
|
||||||
|
!currentSkill.value ||
|
||||||
|
!selectedPath.value ||
|
||||||
|
selectedIsDir.value ||
|
||||||
|
isBuiltinInstalledSkill.value
|
||||||
|
)
|
||||||
|
return
|
||||||
savingFile.value = true
|
savingFile.value = true
|
||||||
try {
|
try {
|
||||||
await skillApi.updateSkillFile(currentSkill.value.slug, {
|
await skillApi.updateSkillFile(currentSkill.value.slug, {
|
||||||
@ -521,7 +572,11 @@ const handleUpdateBuiltin = async (record) => {
|
|||||||
const confirmDeleteSkill = () => {
|
const confirmDeleteSkill = () => {
|
||||||
const target = currentSkill.value
|
const target = currentSkill.value
|
||||||
if (!target) return
|
if (!target) return
|
||||||
const isBuiltinTarget = !!(target?.is_builtin || target?.installed_record || target?.sourceType === 'builtin')
|
const isBuiltinTarget = !!(
|
||||||
|
target?.is_builtin ||
|
||||||
|
target?.installed_record ||
|
||||||
|
target?.sourceType === 'builtin'
|
||||||
|
)
|
||||||
const actionText = isBuiltinTarget ? '卸载' : '删除'
|
const actionText = isBuiltinTarget ? '卸载' : '删除'
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: `确认${actionText}技能「${target.slug}」?`,
|
title: `确认${actionText}技能「${target.slug}」?`,
|
||||||
@ -840,4 +895,4 @@ onMounted(() => {
|
|||||||
.pt-12 {
|
.pt-12 {
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -14,7 +14,10 @@
|
|||||||
</template>
|
</template>
|
||||||
</PageShoulder>
|
</PageShoulder>
|
||||||
|
|
||||||
<div v-if="filteredEnabledSubAgents.length === 0 && filteredDisabledSubAgents.length === 0" class="extension-card-grid-empty-state">
|
<div
|
||||||
|
v-if="filteredEnabledSubAgents.length === 0 && filteredDisabledSubAgents.length === 0"
|
||||||
|
class="extension-card-grid-empty-state"
|
||||||
|
>
|
||||||
<a-empty :image="false" :description="searchQuery ? '无匹配 SubAgent' : '暂无 SubAgent'" />
|
<a-empty :image="false" :description="searchQuery ? '无匹配 SubAgent' : '暂无 SubAgent'" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -27,7 +30,7 @@
|
|||||||
:title="agent.name"
|
:title="agent.name"
|
||||||
:description="agent.description || '暂无描述'"
|
:description="agent.description || '暂无描述'"
|
||||||
:default-icon="BotIcon"
|
:default-icon="BotIcon"
|
||||||
:tags="agent.is_builtin ? [{ name: '内置' }] : [{name: '添加'}]"
|
:tags="agent.is_builtin ? [{ name: '内置' }] : [{ name: '添加' }]"
|
||||||
:status="{ label: '已添加', level: 'success' }"
|
:status="{ label: '已添加', level: 'success' }"
|
||||||
@click="navigateToDetail(agent)"
|
@click="navigateToDetail(agent)"
|
||||||
>
|
>
|
||||||
@ -42,7 +45,7 @@
|
|||||||
:title="agent.name"
|
:title="agent.name"
|
||||||
:description="agent.description || '暂无描述'"
|
:description="agent.description || '暂无描述'"
|
||||||
:default-icon="BotIcon"
|
:default-icon="BotIcon"
|
||||||
:tags="agent.is_builtin ? [{ name: '内置' }] : [{name: '添加'}]"
|
:tags="agent.is_builtin ? [{ name: '内置' }] : [{ name: '添加' }]"
|
||||||
action-label="添加"
|
action-label="添加"
|
||||||
@click="navigateToDetail(agent)"
|
@click="navigateToDetail(agent)"
|
||||||
@action-click="handleSetAgentEnabled(agent, true)"
|
@action-click="handleSetAgentEnabled(agent, true)"
|
||||||
@ -88,7 +91,9 @@
|
|||||||
class="model-selector-full"
|
class="model-selector-full"
|
||||||
@select-model="handleModelSelect"
|
@select-model="handleModelSelect"
|
||||||
/>
|
/>
|
||||||
<a-button v-if="form.model" type="link" size="small" @click="form.model = ''">清空</a-button>
|
<a-button v-if="form.model" type="link" size="small" @click="form.model = ''"
|
||||||
|
>清空</a-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
@ -259,4 +264,4 @@ defineExpose({ fetchSubAgents, loading })
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -140,7 +140,11 @@
|
|||||||
>
|
>
|
||||||
<a-form layout="vertical" class="extension-form">
|
<a-form layout="vertical" class="extension-form">
|
||||||
<a-form-item label="名称" required class="form-item">
|
<a-form-item label="名称" required class="form-item">
|
||||||
<a-input v-model:value="form.name" placeholder="请输入 SubAgent 名称(唯一标识)" disabled />
|
<a-input
|
||||||
|
v-model:value="form.name"
|
||||||
|
placeholder="请输入 SubAgent 名称(唯一标识)"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="描述" class="form-item">
|
<a-form-item label="描述" class="form-item">
|
||||||
<a-input v-model:value="form.description" placeholder="请输入 SubAgent 描述" />
|
<a-input v-model:value="form.description" placeholder="请输入 SubAgent 描述" />
|
||||||
@ -166,7 +170,9 @@
|
|||||||
class="model-selector-full"
|
class="model-selector-full"
|
||||||
@select-model="handleModelSelect"
|
@select-model="handleModelSelect"
|
||||||
/>
|
/>
|
||||||
<a-button v-if="form.model" type="link" size="small" @click="form.model = ''">清空</a-button>
|
<a-button v-if="form.model" type="link" size="small" @click="form.model = ''"
|
||||||
|
>清空</a-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
@ -353,4 +359,4 @@ onMounted(() => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -219,4 +219,4 @@ defineExpose({ fetchTools, loading })
|
|||||||
.config-guide {
|
.config-guide {
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -114,7 +114,9 @@ function emitChange(item) {
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.2s ease, color 0.2s ease;
|
transition:
|
||||||
|
background-color 0.2s ease,
|
||||||
|
color 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--gray-900);
|
color: var(--gray-900);
|
||||||
|
|||||||
@ -23,7 +23,8 @@ export function useModelStatus() {
|
|||||||
const getStatusTooltip = (key) => {
|
const getStatusTooltip = (key) => {
|
||||||
const status = statusMap[key]
|
const status = statusMap[key]
|
||||||
if (!status) return '状态未知'
|
if (!status) return '状态未知'
|
||||||
const text = { available: '可用', unavailable: '不可用', error: '错误' }[status.status] || '未知'
|
const text =
|
||||||
|
{ available: '可用', unavailable: '不可用', error: '错误' }[status.status] || '未知'
|
||||||
return `${text}: ${status.message || '无详细信息'}`
|
return `${text}: ${status.message || '无详细信息'}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,5 +45,12 @@ export function useModelStatus() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { statusMap, getStatusIcon, getStatusClass, getStatusTooltip, checkV2Status, checkV2Statuses }
|
return {
|
||||||
}
|
statusMap,
|
||||||
|
getStatusIcon,
|
||||||
|
getStatusClass,
|
||||||
|
getStatusTooltip,
|
||||||
|
checkV2Status,
|
||||||
|
checkV2Statuses
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -29,4 +29,4 @@ export const modelIcons = {
|
|||||||
zhipu: `${ICON_BASE}/zhipu-color.svg`,
|
zhipu: `${ICON_BASE}/zhipu-color.svg`,
|
||||||
zhipuai: `${ICON_BASE}/zhipu-color.svg`,
|
zhipuai: `${ICON_BASE}/zhipu-color.svg`,
|
||||||
'zhipuai-coding-plan': `${ICON_BASE}/zhipu-color.svg`
|
'zhipuai-coding-plan': `${ICON_BASE}/zhipu-color.svg`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,9 +53,11 @@ const extensionTabs = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const isDetailPage = computed(() => {
|
const isDetailPage = computed(() => {
|
||||||
return route.path.startsWith('/extensions/mcp/') ||
|
return (
|
||||||
|
route.path.startsWith('/extensions/mcp/') ||
|
||||||
route.path.startsWith('/extensions/subagent/') ||
|
route.path.startsWith('/extensions/subagent/') ||
|
||||||
route.path.startsWith('/extensions/skill/')
|
route.path.startsWith('/extensions/skill/')
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const activeChildLoading = computed(() => {
|
const activeChildLoading = computed(() => {
|
||||||
@ -91,4 +93,4 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -256,7 +256,6 @@ function getProviderInfo(provider) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getProviderStatus(provider) {
|
function getProviderStatus(provider) {
|
||||||
if (!provider.is_enabled) return { label: '未启用', level: 'info' }
|
if (!provider.is_enabled) return { label: '未启用', level: 'info' }
|
||||||
if (provider.credential_status === 'warning') return { label: '凭证缺失', level: 'warning' }
|
if (provider.credential_status === 'warning') return { label: '凭证缺失', level: 'warning' }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user