fix(ui): refactor model manager layout and cards

- Tab panel: switch to segment style with fixed header
- Scroll area: isolate panel scroll from tabs
- Card styling: unify text/image/function model cards with rounded corners
- Actions: add tooltips for compact icon-only buttons
This commit is contained in:
linshen committed 2026-03-07 14:23:00 +08:00
1 parent 317afcbd67
commit 1b02031d4a
4 files changed
+415 -210

No files matched your search

@@ -1,11 +1,16 @@
<template>
<div class="function-model-manager">
<NSpace vertical :size="16">
<!-- 评估模型配置 -->
<div class="config-section">
<NSpace align="center" :size="8" class="section-header">
<NCard embedded size="small" :bordered="false" class="function-config-card">
<template #header>
<NSpace justify="space-between" align="center" :size="8" class="section-header">
<NText strong>{{ t('functionModel.evaluationModel') }}</NText>
<NTag size="small" type="default" round :bordered="false">
{{ t('modelManager.textModels') }}
</NTag>
</NSpace>
</template>
<NSpace vertical :size="12" class="config-section">
<NText depth="3" class="section-hint">
{{ t('functionModel.evaluationModelHint') }}
</NText>
@@ -29,23 +34,23 @@
/>
<!-- 显示模型源和模型名称标签 -->
<template v-if="selectedModelInfo">
<NTag v-if="selectedModelInfo.provider" size="small" type="info">
<NTag v-if="selectedModelInfo.provider" size="small" type="default" round :bordered="false">
{{ selectedModelInfo.provider }}
</NTag>
<NTag v-if="selectedModelInfo.model" size="small">
<NTag v-if="selectedModelInfo.model" size="small" type="info" round :bordered="false">
{{ selectedModelInfo.model }}
</NTag>
</template>
</NSpace>
</div>
</NSpace>
</NSpace>
</NCard>
</div>
</template>
<script setup lang="ts">
import { ref, computed, inject, onMounted, type Ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { NSpace, NText, NTag } from 'naive-ui'
import { NCard, NSpace, NText, NTag } from 'naive-ui'
import SelectWithConfig from './SelectWithConfig.vue'
import { useFunctionModelManager } from '../composables/model/useFunctionModelManager'
import { DataTransformer, OptionAccessors } from '../utils/data-transformer'
@@ -164,22 +169,25 @@ defineExpose({ refresh })
<style scoped>
.function-model-manager {
padding: 12px 0;
width: 100%;
}
.function-config-card {
border-radius: 16px;
}
.config-section {
display: flex;
flex-direction: column;
gap: 8px;
gap: 12px;
}
.section-header {
margin-bottom: 4px;
width: 100%;
}
.section-hint {
font-size: 12px;
margin-bottom: 8px;
}
.model-select-row {
+128 -66
View File
@@ -8,50 +8,55 @@
</NEmpty>
<!-- 模型列表 -->
<NSpace v-else vertical :size="12">
<NSpace v-else vertical :size="12" class="image-model-stack">
<NCard
v-for="config in configs"
:key="config.id"
size="small"
hoverable
class="image-model-card"
:style="{
opacity: config.enabled ? 1 : 0.6
}"
>
<template #header>
<NSpace justify="space-between" align="center">
<NSpace vertical :size="2">
<NSpace vertical :size="4" class="image-model-heading">
<!-- 配置名称行 -->
<NSpace align="center">
<NSpace align="center" :size="8">
<NText strong>{{ config.name || config.id }}</NText>
<NTag
v-if="!config.enabled"
type="warning"
size="small"
round
:bordered="false"
>
{{ t('modelManager.disabled') }}
</NTag>
</NSpace>
<!-- 标签行:Provider、Model、能力标签合并 -->
<NSpace :size="6">
<NTag size="small" type="info" :bordered="false">
<NSpace :size="6" class="image-model-tags">
<NTag size="small" type="default" round :bordered="false">
{{ config.provider?.name || config.providerId }}
</NTag>
<NTag size="small" type="primary" :bordered="false">
<NTag size="small" type="info" round :bordered="false">
{{ config.model?.name || config.modelId }}
</NTag>
<NTag
v-if="config.provider?.corsRestricted && !isElectronEnv"
size="small"
type="error"
round
:bordered="false"
>
{{ t('modelManager.corsRestrictedTag') }}
</NTag>
<!-- 能力标签移到这里 -->
<NTag v-if="config.model?.capabilities?.text2image" size="small" type="success" :bordered="false">
<NTag v-if="config.model?.capabilities?.text2image" size="small" type="success" round :bordered="false">
{{ t('image.capability.text2image') }}
</NTag>
<NTag v-if="config.model?.capabilities?.image2image" size="small" type="info" :bordered="false">
<NTag v-if="config.model?.capabilities?.image2image" size="small" type="info" round :bordered="false">
{{ t('image.capability.image2image') }}
</NTag>
</NSpace>
@@ -60,22 +65,28 @@
</template>
<template #header-extra>
<NSpace @click.stop>
<NButton
@click="testConnection(config.id)"
size="small"
quaternary
:disabled="isTestingConnectionFor(config.id)"
:loading="isTestingConnectionFor(config.id)"
>
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
</svg>
<NSpace @click.stop :size="4" class="image-model-actions">
<NTooltip trigger="hover">
<template #trigger>
<NButton
@click="testConnection(config.id)"
size="small"
quaternary
circle
:title="t('modelManager.testConnection')"
:disabled="isTestingConnectionFor(config.id)"
:loading="isTestingConnectionFor(config.id)"
>
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
</svg>
</template>
</NButton>
</template>
<span class="hidden md:inline">{{ t('modelManager.testConnection') }}</span>
</NButton>
{{ t('modelManager.testConnection') }}
</NTooltip>
<!-- 测试结果缩略图 -->
<NImage
@@ -89,53 +100,71 @@
:alt="t('image.connection.testImagePreview')"
/>
<NButton
@click="editConfig(config.id)"
size="small"
quaternary
>
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-4 w-4">
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
<NTooltip trigger="hover">
<template #trigger>
<NButton
@click="editConfig(config.id)"
size="small"
quaternary
circle
:title="t('modelManager.editModel')"
>
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-4 w-4">
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
</template>
</NButton>
</template>
<span class="hidden md:inline">{{ t('modelManager.editModel') }}</span>
</NButton>
{{ t('modelManager.editModel') }}
</NTooltip>
<NButton
@click="toggleConfig(config)"
size="small"
:type="config.enabled ? 'warning' : 'success'"
quaternary
>
<template #icon>
<svg v-if="config.enabled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4">
<path d="M12 6v.343"/>
<path d="M18.218 18.218A7 7 0 0 1 5 15V9a7 7 0 0 1 .782-3.218"/>
<path d="M19 13.343V9A7 7 0 0 0 8.56 2.902"/>
<path d="M22 22 2 2"/>
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4">
<rect x="5" y="2" width="14" height="20" rx="7"/>
<path d="M12 6v4"/>
</svg>
<NTooltip trigger="hover">
<template #trigger>
<NButton
@click="toggleConfig(config)"
size="small"
quaternary
circle
class="image-model-toggle"
:title="config.enabled ? t('common.disable') : t('common.enable')"
>
<template #icon>
<svg v-if="config.enabled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4">
<path d="M12 6v.343"/>
<path d="M18.218 18.218A7 7 0 0 1 5 15V9a7 7 0 0 1 .782-3.218"/>
<path d="M19 13.343V9A7 7 0 0 0 8.56 2.902"/>
<path d="M22 22 2 2"/>
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4">
<rect x="5" y="2" width="14" height="20" rx="7"/>
<path d="M12 6v4"/>
</svg>
</template>
</NButton>
</template>
<span class="hidden md:inline">{{ config.enabled ? t('common.disable') : t('common.enable') }}</span>
</NButton>
{{ config.enabled ? t('common.disable') : t('common.enable') }}
</NTooltip>
<NButton
@click="deleteConfig(config.id)"
size="small"
type="error"
quaternary
>
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-4 w-4">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
<NTooltip trigger="hover">
<template #trigger>
<NButton
@click="deleteConfig(config.id)"
size="small"
type="error"
quaternary
circle
:title="t('common.delete')"
>
<template #icon>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-4 w-4">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.11 0 0 0-7.5 0" />
</svg>
</template>
</NButton>
</template>
<span class="hidden md:inline">{{ t('common.delete') }}</span>
</NButton>
{{ t('common.delete') }}
</NTooltip>
</NSpace>
</template>
</NCard>
@@ -148,7 +177,7 @@ import { ref, onMounted, inject, h } from 'vue'
import { useI18n } from 'vue-i18n'
import {
NSpace, NCard, NText, NTag, NButton, NEmpty, NImage, useDialog
NSpace, NCard, NText, NTag, NButton, NEmpty, NImage, NTooltip, useDialog
} from 'naive-ui'
import { useImageModelManager } from '../composables/model/useImageModelManager'
import { useToast } from '../composables/ui/useToast'
@@ -354,6 +383,39 @@ defineExpose({
width: 100%;
}
.image-model-stack {
width: 100%;
}
.image-model-card {
border-radius: 16px;
}
.image-model-card :deep(.n-card-header) {
padding-bottom: 10px;
}
.image-model-card :deep(.n-card__content) {
padding-top: 0;
}
.image-model-heading,
.image-model-tags {
max-width: 100%;
}
.image-model-tags {
flex-wrap: wrap;
}
.image-model-actions {
flex-wrap: nowrap;
}
.image-model-toggle {
color: var(--n-text-color);
}
/* 文本截断样式 */
.line-clamp-2 {
display: -webkit-box;
+87 -17
View File
@@ -4,6 +4,7 @@
:show="show"
preset="card"
:style="{ width: '90vw', maxWidth: '1200px', maxHeight: '90vh' }"
content-style="padding: 0; display: flex; flex-direction: column; height: min(75vh, 800px); overflow: hidden;"
:title="t('modelManager.title')"
size="large"
:bordered="false"
@@ -63,23 +64,41 @@
</NButton>
</template>
<NScrollbar style="max-height: 75vh;">
<NTabs v-model:value="activeTab" type="line" size="small" style="margin-bottom: 12px;">
<NTabPane name="text" :tab="t('modelManager.textModels')">
<TextModelManager ref="textManagerRef" @models-updated="handleTextModelsUpdated" />
</NTabPane>
<NTabPane name="image" :tab="t('modelManager.imageModels')">
<ImageModelManager
ref="imageListRef"
@edit="handleEditImageModel"
@add="handleAddImageModel"
/>
</NTabPane>
<NTabPane name="function" :tab="t('modelManager.functionModels')">
<FunctionModelManager ref="functionManagerRef" />
</NTabPane>
<div class="model-manager-content">
<NTabs v-model:value="activeTab" type="segment" size="small" animated class="model-manager-tabs">
<NTabPane name="text" :tab="t('modelManager.textModels')" />
<NTabPane name="image" :tab="t('modelManager.imageModels')" />
<NTabPane name="function" :tab="t('modelManager.functionModels')" />
</NTabs>
</NScrollbar>
<div class="model-manager-panel">
<NCard
embedded
size="small"
:bordered="false"
class="model-manager-shell"
content-style="padding: 16px; display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0;"
>
<div class="model-manager-scroll-area">
<TextModelManager
v-show="activeTab === 'text'"
ref="textManagerRef"
@models-updated="handleTextModelsUpdated"
/>
<ImageModelManager
v-show="activeTab === 'image'"
ref="imageListRef"
@edit="handleEditImageModel"
@add="handleAddImageModel"
/>
<FunctionModelManager
v-show="activeTab === 'function'"
ref="functionManagerRef"
/>
</div>
</NCard>
</div>
</div>
</NModal>
<ImageModelEditModal
@@ -95,7 +114,7 @@
import { inject, onMounted, onUnmounted, provide, ref, type Ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { NButton, NModal, NScrollbar, NTabs, NTabPane } from 'naive-ui'
import { NButton, NCard, NModal, NTabs, NTabPane } from 'naive-ui'
import ImageModelEditModal from './ImageModelEditModal.vue'
import ImageModelManager from './ImageModelManager.vue'
import TextModelManager from './TextModelManager.vue'
@@ -184,6 +203,57 @@ if (typeof window !== 'undefined') {
</script>
<style scoped>
.model-manager-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 12px;
min-height: 0;
overflow: hidden;
}
.model-manager-tabs {
flex: 0 0 auto;
}
.model-manager-tabs :deep(.n-tabs-nav) {
margin-bottom: 0;
}
.model-manager-tabs :deep(.n-tabs-nav-scroll-wrapper) {
padding: 2px;
}
.model-manager-tabs :deep(.n-tabs-content) {
display: none;
}
.model-manager-panel {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
.model-manager-shell {
flex: 1;
min-height: 0;
overflow: hidden;
border-radius: 16px;
}
.model-manager-shell :deep(.n-card__content) {
min-height: 0;
}
.model-manager-scroll-area {
flex: 1;
min-height: 0;
overflow: auto;
padding-right: 4px;
}
.modal-enter-active,
.modal-leave-active {
transition: all 0.3s ease;
+179 -114
View File
@@ -1,32 +1,35 @@
<template>
<NSpace vertical :size="12">
<NSpace vertical :size="12" class="model-list-stack">
<NCard
v-for="model in models"
:key="model.id"
size="small"
hoverable
class="model-config-card"
:style="{ opacity: model.enabled ? 1 : 0.6 }"
>
<template #header>
<NSpace justify="space-between" align="center">
<NSpace vertical :size="2">
<NSpace align="center">
<NSpace vertical :size="4" class="model-card-heading">
<NSpace align="center" :size="8">
<NText strong>{{ model.name }}</NText>
<NTag v-if="!model.enabled" type="warning" size="small">
<NTag v-if="!model.enabled" type="warning" size="small" round :bordered="false">
{{ t('modelManager.disabled') }}
</NTag>
</NSpace>
<NSpace :size="6">
<NTag size="small" type="info" :bordered="false">
<NSpace :size="6" class="model-card-tags">
<NTag size="small" type="default" round :bordered="false">
{{ model.providerMeta?.name || model.providerMeta?.id }}
</NTag>
<NTag size="small" type="primary" :bordered="false">
<NTag size="small" type="info" round :bordered="false">
{{ model.modelMeta?.name || model.modelMeta?.id }}
</NTag>
<NTag
v-if="model.providerMeta?.corsRestricted && !isElectronEnv"
size="small"
type="error"
round
:bordered="false"
>
{{ t('modelManager.corsRestrictedTag') }}
@@ -35,6 +38,7 @@
v-if="model.modelMeta?.capabilities?.supportsTools"
size="small"
type="success"
round
:bordered="false"
>
{{ t('modelManager.capabilities.tools') }}
@@ -43,132 +47,158 @@
v-if="model.modelMeta?.capabilities?.supportsReasoning"
size="small"
type="warning"
round
:bordered="false"
>
{{ t('modelManager.capabilities.reasoning') }}
</NTag>
</NSpace>
</NSpace>
</NSpace>
</NSpace>
</template>
<template #header-extra>
<NSpace @click.stop>
<NButton
@click="emit('test', model.id)"
size="small"
quaternary
:disabled="isTestingConnectionFor(model.id)"
:loading="isTestingConnectionFor(model.id)"
>
<template #icon>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
class="h-4 w-4"
<NSpace @click.stop :size="4" class="model-card-actions">
<NTooltip trigger="hover">
<template #trigger>
<NButton
@click="emit('test', model.id)"
size="small"
quaternary
circle
:title="t('modelManager.testConnection')"
:disabled="isTestingConnectionFor(model.id)"
:loading="isTestingConnectionFor(model.id)"
>
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
<template #icon>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
class="h-4 w-4"
>
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</template>
</NButton>
</template>
<span class="hidden md:inline">{{ t('modelManager.testConnection') }}</span>
</NButton>
{{ t('modelManager.testConnection') }}
</NTooltip>
<NButton @click="emit('edit', model.id)" size="small" quaternary>
<template #icon>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-4 w-4"
<NTooltip trigger="hover">
<template #trigger>
<NButton
@click="emit('edit', model.id)"
size="small"
quaternary
circle
:title="t('modelManager.editModel')"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"
/>
</svg>
<template #icon>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-4 w-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"
/>
</svg>
</template>
</NButton>
</template>
<span class="hidden md:inline">{{ t('modelManager.editModel') }}</span>
</NButton>
{{ t('modelManager.editModel') }}
</NTooltip>
<NButton
@click="emit(model.enabled ? 'disable' : 'enable', model.id)"
size="small"
:type="model.enabled ? 'warning' : 'success'"
quaternary
>
<template #icon>
<svg
v-if="model.enabled"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
class="h-4 w-4"
<NTooltip trigger="hover">
<template #trigger>
<NButton
@click="emit(model.enabled ? 'disable' : 'enable', model.id)"
size="small"
quaternary
circle
class="model-card-toggle"
:title="model.enabled ? t('common.disable') : t('common.enable')"
>
<path d="M12 6v.343" />
<path d="M18.218 18.218A7 7 0 0 1 5 15V9a7 7 0 0 1 .782-3.218" />
<path d="M19 13.343V9A7 7 0 0 0 8.56 2.902" />
<path d="M22 22 2 2" />
</svg>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
class="h-4 w-4"
>
<rect x="5" y="2" width="14" height="20" rx="7" />
<path d="M12 6v4" />
</svg>
<template #icon>
<svg
v-if="model.enabled"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
class="h-4 w-4"
>
<path d="M12 6v.343" />
<path d="M18.218 18.218A7 7 0 0 1 5 15V9a7 7 0 0 1 .782-3.218" />
<path d="M19 13.343V9A7 7 0 0 0 8.56 2.902" />
<path d="M22 22 2 2" />
</svg>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
class="h-4 w-4"
>
<rect x="5" y="2" width="14" height="20" rx="7" />
<path d="M12 6v4" />
</svg>
</template>
</NButton>
</template>
<span class="hidden md:inline">
{{ model.enabled ? t('common.disable') : t('common.enable') }}
</span>
</NButton>
{{ model.enabled ? t('common.disable') : t('common.enable') }}
</NTooltip>
<NButton
v-if="!isDefaultModel(model.id)"
@click="emit('delete', model.id)"
size="small"
type="error"
quaternary
>
<template #icon>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-4 w-4"
<NTooltip v-if="!isDefaultModel(model.id)" trigger="hover">
<template #trigger>
<NButton
@click="emit('delete', model.id)"
size="small"
type="error"
quaternary
circle
:title="t('common.delete')"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
/>
</svg>
<template #icon>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-4 w-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.11 0 0 0-7.5 0"
/>
</svg>
</template>
</NButton>
</template>
<span class="hidden md:inline">{{ t('common.delete') }}</span>
</NButton>
{{ t('common.delete') }}
</NTooltip>
</NSpace>
</template>
</NCard>
@@ -179,7 +209,7 @@
import { type PropType } from 'vue'
import { useI18n } from 'vue-i18n'
import { NButton, NCard, NTag, NText, NSpace } from 'naive-ui'
import { NButton, NCard, NTag, NText, NSpace, NTooltip } from 'naive-ui'
import { isRunningInElectron, type TextModelConfig } from '@prompt-optimizer/core'
const { models, isTestingConnectionFor, isDefaultModel } = defineProps({
@@ -203,3 +233,38 @@ const { t } = useI18n()
const isElectronEnv = isRunningInElectron()
</script>
<style scoped>
.model-list-stack {
width: 100%;
}
.model-config-card {
border-radius: 16px;
}
.model-config-card :deep(.n-card-header) {
padding-bottom: 10px;
}
.model-config-card :deep(.n-card__content) {
padding-top: 0;
}
.model-card-heading,
.model-card-tags {
max-width: 100%;
}
.model-card-tags {
flex-wrap: wrap;
}
.model-card-actions {
flex-wrap: nowrap;
}
.model-card-toggle {
color: var(--n-text-color);
}
</style>