feat: 新增 Agent 管理面板 + ModelManageView
- 新增 AgentManagePanel 组件,集中管理 Agent 实例 - ModelConfigView 重命名为 ModelProviderManagePanel - 新增 ModelManageView 页面整合模型与 Agent 管理 - 更新路由、侧边栏导航 - HomeView 调整布局,新增 Agent 管理入口
This commit is contained in:
parent
749ed8d757
commit
a35fd85353
BIN
web/src/assets/defaults/agent.png
Normal file
BIN
web/src/assets/defaults/agent.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
1006
web/src/components/model-management/AgentManagePanel.vue
Normal file
1006
web/src/components/model-management/AgentManagePanel.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,25 +1,14 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import {
|
||||
Globe,
|
||||
Plus,
|
||||
RefreshCw,
|
||||
Search,
|
||||
Settings2,
|
||||
Trash2,
|
||||
CheckCircle2,
|
||||
Edit3
|
||||
} from 'lucide-vue-next'
|
||||
import { Globe, Plus, RefreshCw, Search, Settings2, Trash2, CheckCircle2, Edit3 } from 'lucide-vue-next'
|
||||
|
||||
import { modelProviderApi } from '@/apis/system_api'
|
||||
import { modelIcons } from '@/utils/modelIcon'
|
||||
import PageHeader from '@/components/shared/PageHeader.vue'
|
||||
import PageShoulder from '@/components/shared/PageShoulder.vue'
|
||||
import InfoCard from '@/components/shared/InfoCard.vue'
|
||||
import ExtensionCardGrid from '@/components/extensions/ExtensionCardGrid.vue'
|
||||
|
||||
// ============ State ============
|
||||
const loading = ref(false)
|
||||
const remoteLoading = ref(false)
|
||||
const saving = ref(false)
|
||||
@ -78,8 +67,6 @@ const remoteModelsLoaded = ref({})
|
||||
// Remote model search state per provider
|
||||
const remoteModelSearch = ref({})
|
||||
const remoteModelTypeFilter = ref({})
|
||||
|
||||
// ============ Computed ============
|
||||
const filteredProviders = computed(() => {
|
||||
const keyword = searchQuery.value.trim().toLowerCase()
|
||||
const filtered = keyword
|
||||
@ -234,8 +221,6 @@ const parseJsonObject = (text, label) => {
|
||||
}
|
||||
|
||||
const formatJsonText = (value) => JSON.stringify(value || {}, null, 2)
|
||||
|
||||
// ============ Provider Operations ============
|
||||
const loadProviders = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
@ -251,7 +236,7 @@ const loadProviders = async () => {
|
||||
function getProviderInfo(provider) {
|
||||
return [
|
||||
{ label: 'Base URL', value: provider.base_url || '-' },
|
||||
{ label: '类型', value: provider.provider_type || 'openai' },
|
||||
{ label: '能力', value: provider.capabilities?.join(', ') || 'chat' },
|
||||
{ label: '已启用', value: `${provider.enabled_models?.length || 0} 个模型` }
|
||||
]
|
||||
}
|
||||
@ -564,76 +549,65 @@ const removeModel = async (providerId, modelId) => {
|
||||
})
|
||||
}
|
||||
|
||||
// ============ Lifecycle ============
|
||||
onMounted(loadProviders)
|
||||
|
||||
defineExpose({
|
||||
loading,
|
||||
stats: providerStats,
|
||||
refresh: loadProviders
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="model-config-view">
|
||||
<!-- Page Header -->
|
||||
<PageHeader title="模型配置" :show-border="true">
|
||||
<template #info>
|
||||
<div class="summary-strip">
|
||||
<span>{{ providerStats.total }} 个供应商</span>
|
||||
<span>{{ providerStats.enabled }} 个启用</span>
|
||||
<span v-if="providerStats.warning > 0" class="warning-count"
|
||||
>{{ providerStats.warning }} 个凭证缺失</span
|
||||
>
|
||||
<span>{{ providerStats.models }} 个模型</span>
|
||||
</div>
|
||||
</template>
|
||||
</PageHeader>
|
||||
|
||||
<!-- Toolbar -->
|
||||
<PageShoulder v-model:search="searchQuery" search-placeholder="搜索供应商...">
|
||||
<template #actions>
|
||||
<a-button type="primary" class="lucide-icon-btn" @click="openCreateProviderModal">
|
||||
<Plus :size="14" />
|
||||
新增供应商
|
||||
</a-button>
|
||||
<a-button class="lucide-icon-btn" @click="loadProviders" :loading="loading">
|
||||
<RefreshCw :size="14" :class="{ spinning: loading }" />
|
||||
</a-button>
|
||||
</template>
|
||||
</PageShoulder>
|
||||
|
||||
<!-- Provider Card Grid -->
|
||||
<ExtensionCardGrid :min-width="320">
|
||||
<InfoCard
|
||||
v-for="provider in filteredProviders"
|
||||
:key="provider.provider_id"
|
||||
:title="provider.display_name"
|
||||
:subtitle="provider.provider_id"
|
||||
:disabled="!provider.is_enabled"
|
||||
:default-icon="Globe"
|
||||
:info="getProviderInfo(provider)"
|
||||
:status="getProviderStatus(provider)"
|
||||
@click="openEditProviderModal(provider)"
|
||||
>
|
||||
<template #icon>
|
||||
<img
|
||||
v-if="getProviderIcon(provider)"
|
||||
:src="getIconUrl(getProviderIcon(provider))"
|
||||
:alt="provider.display_name"
|
||||
/>
|
||||
</template>
|
||||
<template #footer>
|
||||
<button class="view-models-btn" type="button" @click.stop="openModelsModal(provider)">
|
||||
<Settings2 :size="14" />
|
||||
管理模型
|
||||
</button>
|
||||
<a-tooltip title="编辑供应商">
|
||||
<a-button
|
||||
size="small"
|
||||
class="lucide-icon-btn"
|
||||
@click.stop="openEditProviderModal(provider)"
|
||||
>
|
||||
<Edit3 :size="14" />
|
||||
<div class="model-provider-manage-panel">
|
||||
<PageShoulder v-model:search="searchQuery" search-placeholder="搜索供应商...">
|
||||
<template #actions>
|
||||
<a-button type="primary" class="lucide-icon-btn" @click="openCreateProviderModal">
|
||||
<Plus :size="14" />
|
||||
新增供应商
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</InfoCard>
|
||||
</ExtensionCardGrid>
|
||||
<a-button class="lucide-icon-btn" @click="loadProviders" :loading="loading">
|
||||
<RefreshCw :size="14" :class="{ spinning: loading }" />
|
||||
</a-button>
|
||||
</template>
|
||||
</PageShoulder>
|
||||
|
||||
<ExtensionCardGrid :min-width="320">
|
||||
<InfoCard
|
||||
v-for="provider in filteredProviders"
|
||||
:key="provider.provider_id"
|
||||
:title="provider.display_name"
|
||||
:subtitle="provider.provider_id"
|
||||
:disabled="!provider.is_enabled"
|
||||
:default-icon="Globe"
|
||||
:info="getProviderInfo(provider)"
|
||||
:status="getProviderStatus(provider)"
|
||||
@click="openEditProviderModal(provider)"
|
||||
>
|
||||
<template #icon>
|
||||
<img
|
||||
v-if="getProviderIcon(provider)"
|
||||
:src="getIconUrl(getProviderIcon(provider))"
|
||||
:alt="provider.display_name"
|
||||
/>
|
||||
</template>
|
||||
<template #footer>
|
||||
<button class="view-models-btn" type="button" @click.stop="openModelsModal(provider)">
|
||||
<Settings2 :size="14" />
|
||||
管理模型
|
||||
</button>
|
||||
<a-tooltip title="编辑供应商">
|
||||
<a-button
|
||||
size="small"
|
||||
class="lucide-icon-btn"
|
||||
@click.stop="openEditProviderModal(provider)"
|
||||
>
|
||||
<Edit3 :size="14" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</InfoCard>
|
||||
</ExtensionCardGrid>
|
||||
|
||||
<!-- Provider Edit Modal -->
|
||||
<a-modal
|
||||
@ -791,7 +765,6 @@ onMounted(loadProviders)
|
||||
</a-collapse>
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
<!-- Models Management Modal -->
|
||||
<a-modal
|
||||
v-model:open="showModelsModal"
|
||||
@ -957,7 +930,6 @@ onMounted(loadProviders)
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
<!-- Model Config Modal -->
|
||||
<a-modal
|
||||
v-model:open="showModelModal"
|
||||
@ -1022,38 +994,13 @@ onMounted(loadProviders)
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<style lang="less" scoped>
|
||||
.model-config-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
background: var(--gray-0);
|
||||
color: var(--gray-1000);
|
||||
.model-provider-manage-panel {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
// ============ Provider Summary Strip ============
|
||||
.summary-strip {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
span {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--gray-100);
|
||||
border-radius: 7px;
|
||||
background: var(--gray-10);
|
||||
color: var(--gray-700);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.warning-count {
|
||||
background: var(--color-warning-50);
|
||||
border-color: var(--color-warning-100);
|
||||
color: var(--color-warning-700);
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Provider Card Footer ============
|
||||
.view-models-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@ -1086,33 +1033,12 @@ onMounted(loadProviders)
|
||||
}
|
||||
}
|
||||
|
||||
.clickable {
|
||||
color: var(--main-700);
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
// ============ Models Modal ============
|
||||
.models-modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.models-modal-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.models-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -1126,12 +1052,6 @@ onMounted(loadProviders)
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
.models-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
// ============ Models Table ============
|
||||
.models-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -1140,10 +1060,15 @@ onMounted(loadProviders)
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-head {
|
||||
.table-head,
|
||||
.table-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 80px 70px 60px 110px;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.table-head {
|
||||
padding: 10px 12px;
|
||||
background: var(--gray-50);
|
||||
font-size: 11px;
|
||||
@ -1154,13 +1079,9 @@ onMounted(loadProviders)
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 80px 70px 60px 110px;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
border-top: 1px solid var(--gray-100);
|
||||
font-size: 13px;
|
||||
align-items: center;
|
||||
transition: background 0.1s;
|
||||
|
||||
&:hover {
|
||||
@ -1196,14 +1117,6 @@ onMounted(loadProviders)
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.col-id {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.col-type {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -1215,18 +1128,12 @@ onMounted(loadProviders)
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.col-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.col-ops {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
// ============ Type Tags ============
|
||||
.type-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@ -1260,53 +1167,6 @@ onMounted(loadProviders)
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Status Badges ============
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
|
||||
&.active {
|
||||
color: var(--color-success-700);
|
||||
}
|
||||
|
||||
&.stale {
|
||||
color: var(--color-warning-700);
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Remote Section ============
|
||||
.remote-section {
|
||||
margin-top: 16px;
|
||||
border: 1px solid var(--gray-150);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.remote-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: none;
|
||||
background: var(--gray-50);
|
||||
color: var(--gray-700);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background 0.1s;
|
||||
|
||||
&:hover {
|
||||
background: var(--gray-100);
|
||||
}
|
||||
}
|
||||
|
||||
.remote-list {
|
||||
border-top: 1px solid var(--gray-100);
|
||||
}
|
||||
|
||||
.enabled-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -1345,6 +1205,10 @@ onMounted(loadProviders)
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.remote-list {
|
||||
border-top: 1px solid var(--gray-100);
|
||||
}
|
||||
|
||||
.remote-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -1413,11 +1277,6 @@ onMounted(loadProviders)
|
||||
}
|
||||
}
|
||||
|
||||
.models-empty {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
// ============ Modal Form ============
|
||||
.modal-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -1466,6 +1325,7 @@ onMounted(loadProviders)
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
:deep(.ant-collapse-header) {
|
||||
padding-inline: 0;
|
||||
}
|
||||
@ -1506,28 +1366,9 @@ onMounted(loadProviders)
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Responsive ============
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
padding: 12px 16px;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.provider-grid {
|
||||
.form-row {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.table-head,
|
||||
@ -145,8 +145,8 @@ const mainList = computed(() => {
|
||||
})
|
||||
|
||||
items.push({
|
||||
name: '模型配置',
|
||||
path: '/model-config',
|
||||
name: '模型管理',
|
||||
path: '/model-manage',
|
||||
icon: Box,
|
||||
activeIcon: Box
|
||||
})
|
||||
|
||||
@ -78,15 +78,15 @@ const router = createRouter({
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/model-config',
|
||||
name: 'model-config',
|
||||
path: '/model-manage',
|
||||
name: 'model-manage',
|
||||
component: AppLayout,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'ModelConfigComp',
|
||||
component: () => import('../views/ModelConfigView.vue'),
|
||||
meta: { keepAlive: false, requiresAuth: true, requiresAdmin: true }
|
||||
name: 'ModelManageComp',
|
||||
component: () => import('../views/ModelManageView.vue'),
|
||||
meta: { keepAlive: false, requiresAuth: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -131,7 +131,6 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useInfoStore } from '@/stores/info'
|
||||
import { useAgentStore } from '@/stores/agent'
|
||||
import { healthApi } from '@/apis/system_api'
|
||||
import UserInfoComponent from '@/components/UserInfoComponent.vue'
|
||||
import {
|
||||
@ -149,7 +148,6 @@ import {
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const infoStore = useInfoStore()
|
||||
const agentStore = useAgentStore()
|
||||
const repoUrl = 'https://github.com/xerrors/Yuxi'
|
||||
const faqUrl = 'https://xerrors.github.io/Yuxi/'
|
||||
|
||||
@ -358,35 +356,13 @@ const retryLoad = () => {
|
||||
}
|
||||
|
||||
const goToChat = async () => {
|
||||
// 检查用户是否登录
|
||||
if (!userStore.isLoggedIn) {
|
||||
// 登录后应该跳转到默认智能体而不是/agent
|
||||
sessionStorage.setItem('redirect', '/') // 设置为首页,登录后会通过路由守卫处理重定向
|
||||
sessionStorage.setItem('redirect', '/')
|
||||
router.push('/login')
|
||||
return
|
||||
}
|
||||
|
||||
// 根据用户角色进行跳转
|
||||
if (userStore.isAdmin) {
|
||||
// 管理员用户跳转到聊天页面
|
||||
await agentStore.initialize()
|
||||
router.push('/agent')
|
||||
return
|
||||
}
|
||||
|
||||
// 普通用户跳转到默认智能体
|
||||
try {
|
||||
// 获取默认智能体
|
||||
const defaultAgent = agentStore.defaultAgent
|
||||
if (defaultAgent?.id) {
|
||||
router.push(`/agent/${defaultAgent.id}`)
|
||||
} else {
|
||||
router.push('/agent')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('跳转到智能体页面失败:', error)
|
||||
router.push('/')
|
||||
}
|
||||
router.push('/agent')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
136
web/src/views/ModelManageView.vue
Normal file
136
web/src/views/ModelManageView.vue
Normal file
@ -0,0 +1,136 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import PageHeader from '@/components/shared/PageHeader.vue'
|
||||
import AgentManagePanel from '@/components/model-management/AgentManagePanel.vue'
|
||||
import ModelProviderManagePanel from '@/components/model-management/ModelProviderManagePanel.vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const activeTab = ref('agents')
|
||||
const agentPanelRef = ref(null)
|
||||
const providerPanelRef = ref(null)
|
||||
|
||||
const modelManageTabs = computed(() => {
|
||||
const tabs = [{ key: 'agents', label: '智能体' }]
|
||||
if (userStore.isAdmin) tabs.push({ key: 'providers', label: '模型供应商' })
|
||||
return tabs
|
||||
})
|
||||
|
||||
const activePanel = computed(() =>
|
||||
activeTab.value === 'providers' ? providerPanelRef.value : agentPanelRef.value
|
||||
)
|
||||
|
||||
const activeLoading = computed(() => activePanel.value?.loading || false)
|
||||
const activeStats = computed(() => activePanel.value?.stats || {})
|
||||
|
||||
const normalizeTab = (tab) => {
|
||||
if (tab === 'providers' && userStore.isAdmin) return 'providers'
|
||||
return 'agents'
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [route.query.tab, userStore.isAdmin],
|
||||
([tab]) => {
|
||||
const nextTab = normalizeTab(tab)
|
||||
if (activeTab.value !== nextTab) activeTab.value = nextTab
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(activeTab, (tab) => {
|
||||
const nextTab = normalizeTab(tab)
|
||||
if (nextTab !== tab) {
|
||||
activeTab.value = nextTab
|
||||
return
|
||||
}
|
||||
if (route.query.tab === nextTab) return
|
||||
router.replace({ query: { ...route.query, tab: nextTab } })
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="model-manage-view">
|
||||
<PageHeader
|
||||
v-model:active-key="activeTab"
|
||||
title="模型管理"
|
||||
:tabs="modelManageTabs"
|
||||
:loading="activeLoading"
|
||||
:show-border="true"
|
||||
aria-label="模型管理视图切换"
|
||||
>
|
||||
<template #info>
|
||||
<div v-if="activeTab === 'agents'" class="summary-strip">
|
||||
<span>{{ activeStats.total || 0 }} 个智能体</span>
|
||||
<span>{{ activeStats.global || 0 }} 个全局</span>
|
||||
<span v-if="activeStats.builtin">{{ activeStats.builtin }} 个内置</span>
|
||||
<span>{{ activeStats.manageable || 0 }} 个可管理</span>
|
||||
</div>
|
||||
<div v-else class="summary-strip">
|
||||
<span>{{ activeStats.total || 0 }} 个供应商</span>
|
||||
<span>{{ activeStats.enabled || 0 }} 个启用</span>
|
||||
<span v-if="activeStats.warning > 0" class="warning-count">
|
||||
{{ activeStats.warning }} 个凭证缺失
|
||||
</span>
|
||||
<span>{{ activeStats.models || 0 }} 个模型</span>
|
||||
</div>
|
||||
</template>
|
||||
</PageHeader>
|
||||
|
||||
<div class="model-manage-content">
|
||||
<div v-show="activeTab === 'agents'" class="tab-panel">
|
||||
<AgentManagePanel ref="agentPanelRef" />
|
||||
</div>
|
||||
<div v-if="userStore.isAdmin && activeTab === 'providers'" class="tab-panel">
|
||||
<ModelProviderManagePanel ref="providerPanelRef" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.model-manage-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
background: var(--gray-0);
|
||||
color: var(--gray-1000);
|
||||
}
|
||||
|
||||
.model-manage-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
|
||||
.tab-panel {
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.summary-strip {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
span {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--gray-100);
|
||||
border-radius: 7px;
|
||||
background: var(--gray-10);
|
||||
color: var(--gray-700);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.warning-count {
|
||||
background: var(--color-warning-50);
|
||||
border-color: var(--color-warning-100);
|
||||
color: var(--color-warning-700);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user