style(format): 格式化代码

This commit is contained in:
Wenjie Zhang 2026-04-27 02:24:52 +08:00
parent ba7c1299a5
commit ee624513f1
29 changed files with 368 additions and 124 deletions

View File

@ -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)
@ -395,7 +397,7 @@ class LightRagKB(KnowledgeBase):
logger.info(
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

View File

@ -284,7 +284,9 @@ class MilvusKB(KnowledgeBase):
"""将文本分割成块"""
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)

View File

@ -415,7 +415,9 @@ class KnowledgeBaseManager:
kb_instance = await self._get_kb_for_database(db_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"""
kb_instance = await self._get_kb_for_database(db_id)
return await kb_instance.index_file(db_id, file_id, operator_id, params=params)

View File

@ -263,6 +263,7 @@ async def save_messages_from_langgraph_state(
elif msg_type == "tool":
await _save_tool_message(conv_repo, msg_dict)
def _extract_interrupt_info(state) -> Any | None:
"""从 LangGraph state 中提取中断信息"""
if hasattr(state, "tasks") and state.tasks:
@ -1086,7 +1087,7 @@ async def stream_agent_resume(
except Exception as e:
logger.error(f"Error saving messages from LangGraph state: {e}")
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)

View File

@ -7,6 +7,7 @@ from typing import Any
import httpx
from sqlalchemy.ext.asyncio import AsyncSession
from yuxi.config.builtin_providers import BUILTIN_PROVIDERS
from yuxi.repositories.model_provider_repository import (
create_model_provider,
delete_model_provider,
@ -14,7 +15,6 @@ from yuxi.repositories.model_provider_repository import (
list_model_providers,
update_model_provider,
)
from yuxi.config.builtin_providers import BUILTIN_PROVIDERS
from yuxi.services.model_cache import is_v2_spec_format
from yuxi.storage.postgres.models_business import ModelProvider

View File

@ -11,7 +11,8 @@ export default defineConfig({
ignoreDeadLinks: [
/localhost/,
/CONTRIBUTING$/,
/docker-compose\.yml$/
/docker-compose\.yml$/,
/^\.\/intro\//
],
markdown: {
config: (md) => {

View File

@ -27,7 +27,9 @@
cursor: pointer;
padding: 4px 8px;
border-radius: 6px;
transition: color 0.15s, background 0.15s;
transition:
color 0.15s,
background 0.15s;
&:hover {
color: var(--gray-700);
@ -90,4 +92,4 @@
min-height: 0;
overflow: hidden;
}
}
}

View File

@ -86,7 +86,6 @@
}
}
// Provider 标签样式
.provider-tag {
padding: 2px 4px;

View File

@ -101,7 +101,12 @@ const emit = defineEmits(['update:value', 'change'])
const v2Models = ref({})
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({
checkingStatus: false
})

View File

@ -530,12 +530,33 @@ const deleteDatabase = () => {
background: var(--gray-100);
color: var(--gray-600);
&.tag-purple { background: #f3e8ff; color: #7c3aed; }
&.tag-blue { background: var(--color-info-50); 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; }
&.tag-purple {
background: #f3e8ff;
color: #7c3aed;
}
&.tag-blue {
background: var(--color-info-50);
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>

View File

@ -125,7 +125,7 @@ const refreshCache = async () => {
}
//
const { statusMap: modelStatusMap, getStatusIcon, getStatusTooltip } = useModelStatus()
useModelStatus()
const state = reactive({
currentModelStatus: null,
checkingStatus: false,
@ -197,7 +197,6 @@ const handleSelectV2Model = (spec) => {
<style lang="less" scoped>
@import '@/assets/css/model-selector-common.less';
//
.status-check-button {
font-size: 12px;

View File

@ -55,11 +55,7 @@
@click="selectTool(tool)"
>
<div class="item-header">
<component
:is="getToolIcon(tool.id) || Wrench"
:size="16"
class="item-icon"
/>
<component :is="getToolIcon(tool.id) || Wrench" :size="16" class="item-icon" />
<span class="item-name">{{ tool.name }}</span>
</div>
<div class="item-details item-details-inline">

View File

@ -12,7 +12,9 @@
</div>
<div class="extension-card-info">
<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 class="extension-card-status">
<slot name="status" />
@ -23,17 +25,17 @@
class="card-action-btn"
:class="`card-action-btn--${actionVariant || 'primary'}`"
@click.stop="$emit('actionClick')"
>{{ actionLabel }}</button>
>
{{ actionLabel }}
</button>
<template v-else-if="status">
<span
v-if="status.label"
class="card-status-tag"
:class="`card-status-tag--${status.level || 'info'}`"
>{{ status.label }}</span>
<span
class="card-status-dot"
:class="`card-status-dot--${statusDotColor}`"
></span>
>{{ status.label }}</span
>
<span class="card-status-dot" :class="`card-status-dot--${statusDotColor}`"></span>
</template>
</template>
</div>
@ -52,7 +54,10 @@
</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">
<span
v-for="(tag, idx) in normalizedTags"
@ -60,7 +65,8 @@
class="card-tag"
:class="tag.color ? `tag-${tag.color}` : ''"
:style="tag.bgColor ? { backgroundColor: tag.bgColor } : {}"
>{{ tag.name }}</span>
>{{ tag.name }}</span
>
</slot>
</div>
@ -116,7 +122,9 @@ const normalizedTags = computed(() => {
border: 1px solid var(--gray-150);
background: linear-gradient(45deg, var(--gray-0) 0%, var(--gray-25) 100%);
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;
&:hover {
@ -206,7 +214,6 @@ const normalizedTags = computed(() => {
gap: 6px;
}
.info-row {
display: flex;
align-items: center;
@ -260,13 +267,34 @@ const normalizedTags = computed(() => {
background: var(--gray-100);
color: var(--gray-600);
&.tag-purple { background: #f3e8ff; color: #7c3aed; }
&.tag-blue { background: var(--color-info-50); 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; }
&.tag-purple {
background: #f3e8ff;
color: #7c3aed;
}
&.tag-blue {
background: var(--color-info-50);
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 {
@ -282,13 +310,17 @@ const normalizedTags = computed(() => {
font-size: 11px;
font-weight: 600;
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;
appearance: none;
background: var(--main-50);
color: var(--main-700);
&:hover, &:focus {
&:hover,
&:focus {
outline: none;
border-color: var(--main-200);
background: var(--main-50);
@ -299,7 +331,8 @@ const normalizedTags = computed(() => {
background: var(--color-error-50);
color: var(--color-error-700);
&:hover, &:focus {
&:hover,
&:focus {
border-color: var(--color-error-200);
background: var(--color-error-50);
color: var(--color-error-800);
@ -319,7 +352,8 @@ const normalizedTags = computed(() => {
font-size: 11px;
font-weight: 600;
&--success, &--on {
&--success,
&--on {
background: var(--color-success-50);
color: var(--color-success-700);
}

View File

@ -1,5 +1,8 @@
<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 />
<div v-if="!$slots.default && items.length === 0" class="extension-card-grid-empty">
<a-empty :image="false" :description="emptyText" />

View File

@ -28,4 +28,4 @@ defineProps({
max-width: 720px;
}
}
</style>
</style>

View File

@ -76,4 +76,4 @@ defineProps({
background-color: transparent;
}
}
</style>
</style>

View File

@ -14,8 +14,14 @@
</template>
</PageShoulder>
<div v-if="filteredEnabledServers.length === 0 && filteredDisabledServers.length === 0" class="extension-card-grid-empty-state">
<a-empty :image="false" :description="searchQuery ? '无匹配 MCP' : '暂无 MCP点击上方按钮添加'" />
<div
v-if="filteredEnabledServers.length === 0 && filteredDisabledServers.length === 0"
class="extension-card-grid-empty-state"
>
<a-empty
:image="false"
:description="searchQuery ? '无匹配 MCP' : '暂无 MCP点击上方按钮添加'"
/>
</div>
<template v-else>
@ -89,7 +95,10 @@ const formModalVisible = ref(false)
const filteredServers = computed(() => {
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
const q = searchQuery.value.toLowerCase()
@ -98,8 +107,12 @@ const filteredServers = computed(() => {
)
})
const filteredEnabledServers = computed(() => filteredServers.value.filter((item) => !!item.enabled))
const filteredDisabledServers = computed(() => filteredServers.value.filter((item) => !item.enabled))
const filteredEnabledServers = computed(() =>
filteredServers.value.filter((item) => !!item.enabled)
)
const filteredDisabledServers = computed(() =>
filteredServers.value.filter((item) => !item.enabled)
)
const getTransportColor = (transport) => {
const colors = { sse: 'orange', stdio: 'green', streamable_http: 'blue' }
@ -109,7 +122,8 @@ const getTransportColor = (transport) => {
const mcpTags = (server) => {
const tags = []
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
}
@ -168,4 +182,4 @@ defineExpose({ fetchServers, loading })
font-size: 18px;
line-height: 1;
}
</style>
</style>

View File

@ -40,7 +40,9 @@
:class="[
'lucide-icon-btn',
'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" />
@ -68,21 +70,31 @@
<div class="info-item">
<label>传输类型</label>
<span>
<a-tag :color="getTransportColor(server.transport)">{{ server.transport }}</a-tag>
<a-tag :color="getTransportColor(server.transport)">{{
server.transport
}}</a-tag>
</span>
</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>
<span>
<a-tag v-for="tag in server.tags" :key="tag">{{ tag }}</a-tag>
</span>
</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">
<label>MCP URL</label>
<span class="code-inline text-break-all">{{ server.url }}</span>
</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>
<pre class="code-pre">{{ JSON.stringify(server.headers, null, 2) }}</pre>
</div>
@ -103,7 +115,9 @@
<div class="info-item" v-if="server.args && server.args.length > 0">
<label>参数</label>
<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>
</div>
<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">
{{ tool.description }}
</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="参数">
<div class="params-list">
<div
@ -194,10 +211,16 @@
>
<div class="param-header">
<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>
</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>
</a-collapse-panel>
@ -272,7 +295,9 @@ const filteredTools = computed(() => {
if (!toolSearchText.value) return tools.value
const search = toolSearchText.value.toLowerCase()
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);
}
}
</style>
</style>

View File

@ -18,7 +18,11 @@
<a-form v-if="formMode === 'form'" layout="vertical" class="extension-form">
<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 label="描述" class="form-item">
<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-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-row :gutter="16">
<a-col :span="12">
<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-col>
<a-col :span="12">
<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-col>
</a-row>
@ -64,14 +82,24 @@
<a-input v-model:value="form.command" placeholder="例如npx 或 /path/to/server" />
</a-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 label="环境变量" class="form-item">
<McpEnvEditor v-model="form.env" />
</a-form-item>
</template>
<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>
@ -79,7 +107,7 @@
<a-textarea
v-model:value="jsonContent"
:rows="15"
placeholder='请输入 JSON 配置'
placeholder="请输入 JSON 配置"
class="json-textarea"
/>
<div class="json-actions">
@ -129,7 +157,10 @@ const form = reactive({
})
const isStdioTransport = computed(
() => String(form.transport || '').trim().toLowerCase() === 'stdio'
() =>
String(form.transport || '')
.trim()
.toLowerCase() === 'stdio'
)
watch(
@ -310,4 +341,4 @@ const handleFormSubmit = async () => {
gap: 8px;
}
}
</style>
</style>

View File

@ -2,7 +2,11 @@
<div class="skill-cards-page extension-page-root">
<PageShoulder search-placeholder="搜索技能..." v-model:search="searchQuery">
<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" />
<span>远程安装</span>
</a-button>
@ -26,12 +30,17 @@
</template>
</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="无匹配技能" />
</div>
<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>
<ExtensionCard
v-for="skill in filteredInstalledSkills"
@ -46,7 +55,9 @@
</ExtensionCard>
</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">
<ExtensionCard
v-for="skill in filteredUninstalledBuiltinSkills"
@ -102,13 +113,24 @@
/>
</a-form-item>
<div class="remote-install-actions">
<a-button :loading="listingRemoteSkills" :disabled="installingRemoteSkill" @click="handleListRemoteSkills">
<a-button
:loading="listingRemoteSkills"
:disabled="installingRemoteSkill"
@click="handleListRemoteSkills"
>
查看可安装 Skills
</a-button>
<a-button type="primary" :loading="installingRemoteSkill" :disabled="listingRemoteSkills" @click="handleInstallRemoteSkill">
<a-button
type="primary"
:loading="installingRemoteSkill"
:disabled="listingRemoteSkills"
@click="handleInstallRemoteSkill"
>
安装
</a-button>
<span v-if="remoteInstallStatusText" class="remote-install-status">{{ remoteInstallStatusText }}</span>
<span v-if="remoteInstallStatusText" class="remote-install-status">{{
remoteInstallStatusText
}}</span>
</div>
<div v-if="remoteSkillOptions.length" class="remote-skill-summary">
共发现 {{ remoteSkillOptions.length }} skills可按输入内容筛选候选项
@ -174,9 +196,10 @@ const installedSkillCards = computed(() => {
...skill,
sourceType: 'builtin',
sourceLabel: '内置',
status: skill.status === 'update_available'
? { label: '更新可用', level: 'warning' }
: { label: '已安装', level: 'success' }
status:
skill.status === 'update_available'
? { label: '更新可用', level: 'warning' }
: { label: '已安装', level: 'success' }
}
])
)
@ -194,7 +217,9 @@ const installedSkillCards = computed(() => {
const filteredInstalledSkills = computed(() => installedSkillCards.value.filter(matchesSearch))
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(() =>
@ -208,7 +233,8 @@ const remoteInstallStatusText = computed(() => {
if (!remoteInstallProgress.visible || !remoteInstallProgress.total) return ''
const progressText = `[${remoteInstallProgress.completed}/${remoteInstallProgress.total}]`
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()
})
@ -401,12 +427,16 @@ onMounted(() => {
fetchSkills()
})
defineExpose({ fetchSkills, handleImportUpload, openRemoteInstallModal: handleOpenRemoteInstall, loading })
defineExpose({
fetchSkills,
handleImportUpload,
openRemoteInstallModal: handleOpenRemoteInstall,
loading
})
</script>
<style lang="less" scoped>
@import '@/assets/css/extensions.less';
</style>
<style lang="less" scoped>
@ -469,4 +499,4 @@ defineExpose({ fetchSkills, handleImportUpload, openRemoteInstallModal: handleOp
color: var(--gray-500);
}
}
</style>
</style>

View File

@ -19,7 +19,11 @@
</div>
<div class="detail-actions">
<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 }}
</span>
<button
@ -66,7 +70,9 @@
<div v-if="!isInstalledSkill" class="builtin-uninstalled-state">
<h3>{{ currentSkill.description }}</h3>
<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>
<a-tabs v-else v-model:activeKey="activeTab" class="minimal-tabs">
@ -79,9 +85,15 @@
<div class="tree-header">
<span class="label">项目结构</span>
<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="新建目录"><button @click="openCreateModal(true)"><FolderPlus :size="14" /></button></a-tooltip>
<a-tooltip title="刷新"><button @click="reloadTree"><RotateCw :size="14" /></button></a-tooltip>
<a-tooltip v-if="!isBuiltinInstalledSkill" title="新建文件"
><button @click="openCreateModal(false)"><FilePlus :size="14" /></button
></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 class="tree-content">
@ -127,7 +139,11 @@
</div>
</div>
<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>
<MdPreview
v-if="viewMode === 'preview'"
@ -172,13 +188,34 @@
<div class="config-form">
<a-form layout="vertical">
<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 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 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>
</div>
@ -270,11 +307,17 @@ const dependencyForm = reactive({
})
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(() => {
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(() => {
@ -312,7 +355,9 @@ const mcpDependencyOptions = computed(() =>
(dependencyOptions.mcps || []).map((i) => ({ label: i, value: i }))
)
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 = () => {
@ -451,7 +496,13 @@ const handleTreeSelect = async (keys, info) => {
}
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
try {
await skillApi.updateSkillFile(currentSkill.value.slug, {
@ -521,7 +572,11 @@ const handleUpdateBuiltin = async (record) => {
const confirmDeleteSkill = () => {
const target = currentSkill.value
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 ? '卸载' : '删除'
Modal.confirm({
title: `确认${actionText}技能「${target.slug}」?`,
@ -840,4 +895,4 @@ onMounted(() => {
.pt-12 {
padding-top: 12px;
}
</style>
</style>

View File

@ -14,7 +14,10 @@
</template>
</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'" />
</div>
@ -27,7 +30,7 @@
:title="agent.name"
:description="agent.description || '暂无描述'"
:default-icon="BotIcon"
:tags="agent.is_builtin ? [{ name: '内置' }] : [{name: '添加'}]"
:tags="agent.is_builtin ? [{ name: '内置' }] : [{ name: '添加' }]"
:status="{ label: '已添加', level: 'success' }"
@click="navigateToDetail(agent)"
>
@ -42,7 +45,7 @@
:title="agent.name"
:description="agent.description || '暂无描述'"
:default-icon="BotIcon"
:tags="agent.is_builtin ? [{ name: '内置' }] : [{name: '添加'}]"
:tags="agent.is_builtin ? [{ name: '内置' }] : [{ name: '添加' }]"
action-label="添加"
@click="navigateToDetail(agent)"
@action-click="handleSetAgentEnabled(agent, true)"
@ -88,7 +91,9 @@
class="model-selector-full"
@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>
</a-form-item>
</a-form>
@ -259,4 +264,4 @@ defineExpose({ fetchSubAgents, loading })
width: 100%;
}
}
</style>
</style>

View File

@ -140,7 +140,11 @@
>
<a-form layout="vertical" class="extension-form">
<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 label="描述" class="form-item">
<a-input v-model:value="form.description" placeholder="请输入 SubAgent 描述" />
@ -166,7 +170,9 @@
class="model-selector-full"
@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>
</a-form-item>
</a-form>
@ -353,4 +359,4 @@ onMounted(() => {
width: 100%;
}
}
</style>
</style>

View File

@ -219,4 +219,4 @@ defineExpose({ fetchTools, loading })
.config-guide {
white-space: pre-line;
}
</style>
</style>

View File

@ -114,7 +114,9 @@ function emitChange(item) {
line-height: 1;
text-decoration: none;
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease;
transition:
background-color 0.2s ease,
color 0.2s ease;
&:hover {
color: var(--gray-900);

View File

@ -23,7 +23,8 @@ export function useModelStatus() {
const getStatusTooltip = (key) => {
const status = statusMap[key]
if (!status) return '状态未知'
const text = { available: '可用', unavailable: '不可用', error: '错误' }[status.status] || '未知'
const text =
{ available: '可用', unavailable: '不可用', error: '错误' }[status.status] || '未知'
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
}
}

View File

@ -29,4 +29,4 @@ export const modelIcons = {
zhipu: `${ICON_BASE}/zhipu-color.svg`,
zhipuai: `${ICON_BASE}/zhipu-color.svg`,
'zhipuai-coding-plan': `${ICON_BASE}/zhipu-color.svg`
}
}

View File

@ -53,9 +53,11 @@ const extensionTabs = [
]
const isDetailPage = computed(() => {
return route.path.startsWith('/extensions/mcp/') ||
return (
route.path.startsWith('/extensions/mcp/') ||
route.path.startsWith('/extensions/subagent/') ||
route.path.startsWith('/extensions/skill/')
)
})
const activeChildLoading = computed(() => {
@ -91,4 +93,4 @@ watch(
}
}
}
</style>
</style>

View File

@ -256,7 +256,6 @@ function getProviderInfo(provider) {
]
}
function getProviderStatus(provider) {
if (!provider.is_enabled) return { label: '未启用', level: 'info' }
if (provider.credential_status === 'warning') return { label: '凭证缺失', level: 'warning' }