🎨 优化模型配置 && 新增apikey功能 && 完善通用接口
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
ENV = 'production'
|
||||
|
||||
#下方为上线需要用到的程序代理前缀,一般用于nginx代理转发
|
||||
# API 路径前缀
|
||||
VITE_BASE_API = /api
|
||||
VITE_FILE_API = /api
|
||||
#下方修改为你的线上ip(如果需要在线使用表单构建工具时使用,其余情况无需使用以下环境变量)
|
||||
VITE_BASE_PATH = https://demo.gin-vue-admin.com
|
||||
# 基础路径(部署时会自动使用当前域名)
|
||||
VITE_BASE_PATH = /
|
||||
|
||||
46
web/src/api/aiApiKey.js
Normal file
46
web/src/api/aiApiKey.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import service from '@/utils/request'
|
||||
|
||||
// 创建API密钥
|
||||
export const createAiApiKey = (data) => {
|
||||
return service({
|
||||
url: '/aiApiKey/createAiApiKey',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除API密钥
|
||||
export const deleteAiApiKey = (data) => {
|
||||
return service({
|
||||
url: '/aiApiKey/deleteAiApiKey',
|
||||
method: 'delete',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 更新API密钥
|
||||
export const updateAiApiKey = (data) => {
|
||||
return service({
|
||||
url: '/aiApiKey/updateAiApiKey',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 查询API密钥
|
||||
export const findAiApiKey = (params) => {
|
||||
return service({
|
||||
url: '/aiApiKey/findAiApiKey',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 获取API密钥列表
|
||||
export const getAiApiKeyList = (params) => {
|
||||
return service({
|
||||
url: '/aiApiKey/getAiApiKeyList',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
55
web/src/api/aiModel.js
Normal file
55
web/src/api/aiModel.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import service from '@/utils/request'
|
||||
|
||||
// 创建模型
|
||||
export const createAiModel = (data) => {
|
||||
return service({
|
||||
url: '/aiModel/createAiModel',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除模型
|
||||
export const deleteAiModel = (data) => {
|
||||
return service({
|
||||
url: '/aiModel/deleteAiModel',
|
||||
method: 'delete',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 更新模型
|
||||
export const updateAiModel = (data) => {
|
||||
return service({
|
||||
url: '/aiModel/updateAiModel',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 查询模型
|
||||
export const findAiModel = (params) => {
|
||||
return service({
|
||||
url: '/aiModel/findAiModel',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 获取模型列表
|
||||
export const getAiModelList = (params) => {
|
||||
return service({
|
||||
url: '/aiModel/getAiModelList',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 同步提供商模型
|
||||
export const syncProviderModels = (data) => {
|
||||
return service({
|
||||
url: '/aiModel/syncProviderModels',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export const getAiPresetList = (params) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 导入预设
|
||||
// 导入预设(JSON粘贴)
|
||||
export const importAiPreset = (data) => {
|
||||
return service({
|
||||
url: '/aiPreset/importAiPreset',
|
||||
@@ -53,3 +53,17 @@ export const importAiPreset = (data) => {
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 导入预设(文件上传)
|
||||
export const importAiPresetFile = (file) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return service({
|
||||
url: '/aiPreset/importAiPresetFile',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"/src/view/about/index.vue": "About",
|
||||
"/src/view/ai/apikey/index.vue": "Index",
|
||||
"/src/view/ai/binding/index.vue": "Index",
|
||||
"/src/view/ai/model/index.vue": "Index",
|
||||
"/src/view/ai/preset/components/PromptEditor.vue": "PromptEditor",
|
||||
"/src/view/ai/preset/index.vue": "Index",
|
||||
"/src/view/ai/provider/index.vue": "Index",
|
||||
"/src/view/dashboard/components/banner.vue": "Banner",
|
||||
|
||||
392
web/src/view/ai/apikey/index.vue
Normal file
392
web/src/view/ai/apikey/index.vue
Normal file
@@ -0,0 +1,392 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form :inline="true" :model="searchInfo" class="demo-form-inline">
|
||||
<el-form-item label="密钥名称">
|
||||
<el-input v-model="searchInfo.name" placeholder="搜索密钥名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit">查询</el-button>
|
||||
<el-button @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button type="primary" @click="openDialog">新增</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" style="width: 100%">
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="ID" prop="ID" width="80" />
|
||||
<el-table-column label="名称" prop="name" width="200" />
|
||||
<el-table-column label="API Key" prop="key" min-width="300" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span class="key-text">{{ maskKey(scope.row.key) }}</span>
|
||||
<el-button type="primary" link size="small" @click="copyKey(scope.row.key)">
|
||||
复制
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="允许的模型" width="150">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.allowed_models?.length > 0" type="info">
|
||||
{{ scope.row.allowed_models.length }} 个
|
||||
</el-tag>
|
||||
<el-tag v-else type="success">全部</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="速率限制" width="120">
|
||||
<template #default="scope">
|
||||
{{ scope.row.rate_limit || '无限制' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="过期时间" width="180">
|
||||
<template #default="scope">
|
||||
{{ scope.row.expires_at ? formatDate(scope.row.expires_at * 1000) : '永不过期' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="enabled" width="80">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.enabled" @change="handleStatusChange(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" prop="CreatedAt" width="180">
|
||||
<template #default="scope">{{ formatDate(scope.row.CreatedAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="200">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="updateApiKey(scope.row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="deleteApiKey(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="dialogFormVisible"
|
||||
:title="type === 'create' ? '新增API密钥' : '编辑API密钥'"
|
||||
width="700px"
|
||||
>
|
||||
<el-form ref="apiKeyForm" :model="formData" :rules="rules" label-width="120px">
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入密钥名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="type === 'create'" label="API Key" prop="key">
|
||||
<el-input v-model="formData.key" placeholder="留空自动生成" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="允许的模型">
|
||||
<el-select
|
||||
v-model="formData.allowed_models"
|
||||
multiple
|
||||
filterable
|
||||
allow-create
|
||||
placeholder="留空表示允许所有模型"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="model in availableModels"
|
||||
:key="model"
|
||||
:label="model"
|
||||
:value="model"
|
||||
/>
|
||||
</el-select>
|
||||
<div style="margin-top: 5px; color: #909399; font-size: 12px">
|
||||
模型列表来自提供商配置,也可手动输入其他模型名称
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="允许的预设">
|
||||
<el-select
|
||||
v-model="formData.allowed_presets"
|
||||
multiple
|
||||
filterable
|
||||
allow-create
|
||||
placeholder="留空表示允许所有预设"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="preset in presetList"
|
||||
:key="preset.name"
|
||||
:label="preset.name"
|
||||
:value="preset.name"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="速率限制">
|
||||
<el-input-number
|
||||
v-model="formData.rate_limit"
|
||||
:min="0"
|
||||
placeholder="每分钟请求数,0表示不限制"
|
||||
/>
|
||||
<span style="margin-left: 10px; color: #909399">次/分钟</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="过期时间">
|
||||
<el-date-picker
|
||||
v-model="expiresDate"
|
||||
type="datetime"
|
||||
placeholder="选择过期时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用" prop="enabled">
|
||||
<el-switch v-model="formData.enabled" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeDialog">取消</el-button>
|
||||
<el-button type="primary" @click="enterDialog">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
getAiApiKeyList,
|
||||
createAiApiKey,
|
||||
updateAiApiKey,
|
||||
deleteAiApiKey
|
||||
} from '@/api/aiApiKey'
|
||||
import { getAiPresetList } from '@/api/aiPreset'
|
||||
import { getAiProviderList } from '@/api/aiProvider'
|
||||
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
const tableData = ref([])
|
||||
const searchInfo = reactive({ name: '' })
|
||||
const presetList = ref([])
|
||||
const providerList = ref([])
|
||||
const availableModels = computed(() => {
|
||||
const models = new Set()
|
||||
providerList.value.forEach(provider => {
|
||||
if (provider.model) {
|
||||
models.add(provider.model)
|
||||
}
|
||||
})
|
||||
return Array.from(models)
|
||||
})
|
||||
|
||||
const dialogFormVisible = ref(false)
|
||||
const type = ref('')
|
||||
const apiKeyForm = ref(null)
|
||||
const expiresDate = ref(null)
|
||||
|
||||
const formData = ref({
|
||||
name: '',
|
||||
key: '',
|
||||
allowed_models: [],
|
||||
allowed_presets: [],
|
||||
rate_limit: 0,
|
||||
expires_at: null,
|
||||
enabled: true
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: '请输入密钥名称', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const maskKey = (key) => {
|
||||
if (!key) return ''
|
||||
if (key.length <= 12) return key
|
||||
return key.substring(0, 8) + '...' + key.substring(key.length - 4)
|
||||
}
|
||||
|
||||
const copyKey = (key) => {
|
||||
navigator.clipboard.writeText(key)
|
||||
ElMessage.success('已复制到剪贴板')
|
||||
}
|
||||
|
||||
const formatDate = (date) => {
|
||||
if (!date) return ''
|
||||
return new Date(date).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
const getTableData = async () => {
|
||||
const table = await getAiApiKeyList({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
...searchInfo
|
||||
})
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
const loadPresets = async () => {
|
||||
const res = await getAiPresetList({ page: 1, pageSize: 100 })
|
||||
if (res.code === 0) {
|
||||
presetList.value = res.data.list
|
||||
}
|
||||
}
|
||||
|
||||
const loadProviders = async () => {
|
||||
const res = await getAiProviderList({ page: 1, pageSize: 100 })
|
||||
if (res.code === 0) {
|
||||
providerList.value = res.data.list
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
page.value = 1
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
searchInfo.name = ''
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleStatusChange = async (row) => {
|
||||
const res = await updateAiApiKey(row)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('状态更新成功')
|
||||
} else {
|
||||
row.enabled = !row.enabled
|
||||
ElMessage.error('状态更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
expiresDate.value = null
|
||||
formData.value = {
|
||||
name: '',
|
||||
key: '',
|
||||
allowed_models: [],
|
||||
allowed_presets: [],
|
||||
rate_limit: 0,
|
||||
expires_at: null,
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
|
||||
const enterDialog = async () => {
|
||||
apiKeyForm.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
// 处理过期时间
|
||||
if (expiresDate.value) {
|
||||
formData.value.expires_at = Math.floor(new Date(expiresDate.value).getTime() / 1000)
|
||||
} else {
|
||||
formData.value.expires_at = null
|
||||
}
|
||||
|
||||
let res
|
||||
if (type.value === 'create') {
|
||||
res = await createAiApiKey(formData.value)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('创建成功')
|
||||
if (res.data?.key) {
|
||||
ElMessageBox.alert(
|
||||
`API Key: ${res.data.key}`,
|
||||
'请保存您的API密钥',
|
||||
{
|
||||
confirmButtonText: '复制',
|
||||
callback: () => {
|
||||
copyKey(res.data.key)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res = await updateAiApiKey(formData.value)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('更新成功')
|
||||
}
|
||||
}
|
||||
if (res.code === 0) {
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const updateApiKey = (row) => {
|
||||
type.value = 'update'
|
||||
formData.value = { ...row }
|
||||
if (row.expires_at) {
|
||||
expiresDate.value = new Date(row.expires_at * 1000)
|
||||
}
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
const deleteApiKey = (row) => {
|
||||
ElMessageBox.confirm('确定要删除该API密钥吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const res = await deleteAiApiKey({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('删除成功')
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getTableData()
|
||||
loadPresets()
|
||||
loadProviders()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.gva-search-box {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.gva-table-box {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.gva-btn-list {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.gva-pagination {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.key-text {
|
||||
font-family: monospace;
|
||||
color: #606266;
|
||||
}
|
||||
</style>
|
||||
491
web/src/view/ai/model/index.vue
Normal file
491
web/src/view/ai/model/index.vue
Normal file
@@ -0,0 +1,491 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form :inline="true" :model="searchInfo" class="demo-form-inline">
|
||||
<el-form-item label="显示模式">
|
||||
<el-radio-group v-model="viewMode" @change="handleViewModeChange">
|
||||
<el-radio-button value="list">列表视图</el-radio-button>
|
||||
<el-radio-button value="group">分组视图</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="模型名称">
|
||||
<el-input v-model="searchInfo.name" placeholder="搜索模型名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit">查询</el-button>
|
||||
<el-button @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 列表视图 -->
|
||||
<div v-if="viewMode === 'list'" class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button type="primary" @click="openDialog">新增</el-button>
|
||||
<el-button type="success" @click="openSyncDialog">同步模型</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" style="width: 100%">
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="ID" prop="ID" width="80" />
|
||||
<el-table-column label="模型名称" prop="name" width="200" />
|
||||
<el-table-column label="显示名称" prop="display_name" width="150" />
|
||||
<el-table-column label="提供商" width="150">
|
||||
<template #default="scope">
|
||||
<el-tag>{{ scope.row.provider?.name || '-' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="绑定预设" width="150">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.preset" type="success">
|
||||
{{ scope.row.preset.name }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="info">未绑定</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Max Tokens" prop="max_tokens" width="120" />
|
||||
<el-table-column label="状态" prop="enabled" width="80">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.enabled" @change="handleStatusChange(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="描述" prop="description" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" fixed="right" width="200">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="updateModel(scope.row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="deleteModel(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分组视图 -->
|
||||
<div v-else class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button type="primary" @click="openDialog">新增</el-button>
|
||||
<el-button type="success" @click="openSyncDialog">同步模型</el-button>
|
||||
</div>
|
||||
<el-collapse v-model="activeProviders" accordion>
|
||||
<el-collapse-item
|
||||
v-for="provider in groupedModels"
|
||||
:key="provider.id"
|
||||
:name="provider.id"
|
||||
>
|
||||
<template #title>
|
||||
<div class="provider-header">
|
||||
<el-tag :type="provider.enabled ? 'success' : 'info'" style="margin-right: 10px">
|
||||
{{ provider.name }}
|
||||
</el-tag>
|
||||
<span class="model-count">{{ provider.models.length }} 个模型</span>
|
||||
<span class="enabled-count">
|
||||
({{ provider.models.filter(m => m.enabled).length }} 已启用)
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-table :data="provider.models" style="width: 100%">
|
||||
<el-table-column label="模型名称" prop="name" width="200" />
|
||||
<el-table-column label="显示名称" prop="display_name" width="150" />
|
||||
<el-table-column label="绑定预设" width="150">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.preset" type="success">
|
||||
{{ scope.row.preset.name }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="info">未绑定</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Max Tokens" prop="max_tokens" width="120" />
|
||||
<el-table-column label="状态" prop="enabled" width="80">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.enabled" @change="handleStatusChange(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="描述" prop="description" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" fixed="right" width="200">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="updateModel(scope.row)">编辑</el-button>
|
||||
<el-button type="danger" link @click="deleteModel(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<el-dialog
|
||||
v-model="dialogFormVisible"
|
||||
:title="type === 'create' ? '新增模型' : '编辑模型'"
|
||||
width="700px"
|
||||
>
|
||||
<el-form ref="modelForm" :model="formData" :rules="rules" label-width="120px">
|
||||
<el-form-item label="模型名称" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="如: gpt-4" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="显示名称" prop="display_name">
|
||||
<el-input v-model="formData.display_name" placeholder="如: GPT-4" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="提供商" prop="provider_id">
|
||||
<el-select v-model="formData.provider_id" placeholder="请选择提供商" style="width: 100%">
|
||||
<el-option
|
||||
v-for="provider in providerList"
|
||||
:key="provider.ID"
|
||||
:label="provider.name"
|
||||
:value="provider.ID"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="绑定预设" prop="preset_id">
|
||||
<el-select
|
||||
v-model="formData.preset_id"
|
||||
placeholder="选择要绑定的预设(可选)"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="preset in presetList"
|
||||
:key="preset.ID"
|
||||
:label="preset.name"
|
||||
:value="preset.ID"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="Max Tokens" prop="max_tokens">
|
||||
<el-input-number v-model="formData.max_tokens" :min="1" :max="200000" />
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="description">
|
||||
<el-input
|
||||
v-model="formData.description"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="模型描述"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用" prop="enabled">
|
||||
<el-switch v-model="formData.enabled" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeDialog">取消</el-button>
|
||||
<el-button type="primary" @click="enterDialog">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 同步模型对话框 -->
|
||||
<el-dialog v-model="syncDialogVisible" title="同步提供商模型" width="500px">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="选择提供商">
|
||||
<el-select v-model="syncProviderId" placeholder="请选择提供商" style="width: 100%">
|
||||
<el-option
|
||||
v-for="provider in providerList"
|
||||
:key="provider.ID"
|
||||
:label="provider.name"
|
||||
:value="provider.ID"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-alert
|
||||
title="同步说明"
|
||||
type="info"
|
||||
:closable="false"
|
||||
style="margin-top: 10px"
|
||||
>
|
||||
<p>将从提供商的 /v1/models 接口获取可用模型列表</p>
|
||||
<p>新模型将被添加到系统中(默认禁用状态)</p>
|
||||
<p>已存在的模型不会被修改</p>
|
||||
</el-alert>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="syncDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSync" :loading="syncing">开始同步</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
getAiModelList,
|
||||
createAiModel,
|
||||
updateAiModel,
|
||||
deleteAiModel,
|
||||
syncProviderModels
|
||||
} from '@/api/aiModel'
|
||||
import { getAiProviderList } from '@/api/aiProvider'
|
||||
import { getAiPresetList } from '@/api/aiPreset'
|
||||
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
const tableData = ref([])
|
||||
const searchInfo = reactive({ name: '' })
|
||||
const providerList = ref([])
|
||||
const presetList = ref([])
|
||||
const viewMode = ref('list')
|
||||
const activeProviders = ref([])
|
||||
|
||||
const dialogFormVisible = ref(false)
|
||||
const syncDialogVisible = ref(false)
|
||||
const type = ref('')
|
||||
const modelForm = ref(null)
|
||||
const syncProviderId = ref(null)
|
||||
const syncing = ref(false)
|
||||
|
||||
const formData = ref({
|
||||
name: '',
|
||||
display_name: '',
|
||||
provider_id: null,
|
||||
preset_id: null,
|
||||
max_tokens: 4096,
|
||||
description: '',
|
||||
enabled: true
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: '请输入模型名称', trigger: 'blur' }],
|
||||
provider_id: [{ required: true, message: '请选择提供商', trigger: 'change' }]
|
||||
})
|
||||
|
||||
// 按提供商分组模型
|
||||
const groupedModels = computed(() => {
|
||||
const groups = {}
|
||||
|
||||
tableData.value.forEach(model => {
|
||||
const providerId = model.provider?.ID
|
||||
const providerName = model.provider?.name || '未知提供商'
|
||||
|
||||
if (!groups[providerId]) {
|
||||
groups[providerId] = {
|
||||
id: providerId,
|
||||
name: providerName,
|
||||
enabled: model.provider?.enabled || false,
|
||||
models: []
|
||||
}
|
||||
}
|
||||
|
||||
groups[providerId].models.push(model)
|
||||
})
|
||||
|
||||
return Object.values(groups).sort((a, b) => a.name.localeCompare(b.name))
|
||||
})
|
||||
|
||||
const formatDate = (date) => {
|
||||
if (!date) return ''
|
||||
return new Date(date).toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
const handleViewModeChange = () => {
|
||||
if (viewMode.value === 'group') {
|
||||
// 切换到分组视图时,默认展开第一个提供商
|
||||
if (groupedModels.value.length > 0) {
|
||||
activeProviders.value = [groupedModels.value[0].id]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getTableData = async () => {
|
||||
const table = await getAiModelList({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
...searchInfo
|
||||
})
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
const loadProviders = async () => {
|
||||
const res = await getAiProviderList({ page: 1, pageSize: 100 })
|
||||
if (res.code === 0) {
|
||||
providerList.value = res.data.list
|
||||
}
|
||||
}
|
||||
|
||||
const loadPresets = async () => {
|
||||
const res = await getAiPresetList({ page: 1, pageSize: 100 })
|
||||
if (res.code === 0) {
|
||||
presetList.value = res.data.list
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
page.value = 1
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
searchInfo.name = ''
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleStatusChange = async (row) => {
|
||||
const res = await updateAiModel(row)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('状态更新成功')
|
||||
} else {
|
||||
row.enabled = !row.enabled
|
||||
ElMessage.error('状态更新失败')
|
||||
}
|
||||
}
|
||||
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
formData.value = {
|
||||
name: '',
|
||||
display_name: '',
|
||||
provider_id: null,
|
||||
preset_id: null,
|
||||
max_tokens: 4096,
|
||||
description: '',
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
|
||||
const enterDialog = async () => {
|
||||
modelForm.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
let res
|
||||
if (type.value === 'create') {
|
||||
res = await createAiModel(formData.value)
|
||||
} else {
|
||||
res = await updateAiModel(formData.value)
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage.success(type.value === 'create' ? '创建成功' : '更新成功')
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const updateModel = (row) => {
|
||||
type.value = 'update'
|
||||
formData.value = { ...row, provider_id: row.provider?.ID, preset_id: row.preset?.ID }
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
const deleteModel = (row) => {
|
||||
ElMessageBox.confirm('确定要删除该模型吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const res = await deleteAiModel({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('删除成功')
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const openSyncDialog = () => {
|
||||
syncProviderId.value = null
|
||||
syncDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleSync = async () => {
|
||||
if (!syncProviderId.value) {
|
||||
ElMessage.warning('请选择提供商')
|
||||
return
|
||||
}
|
||||
|
||||
syncing.value = true
|
||||
try {
|
||||
const res = await syncProviderModels({ ID: syncProviderId.value })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('同步成功')
|
||||
syncDialogVisible.value = false
|
||||
getTableData()
|
||||
}
|
||||
} finally {
|
||||
syncing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
loadProviders()
|
||||
loadPresets()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.gva-search-box {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.gva-table-box {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.gva-btn-list {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.gva-pagination {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.provider-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.model-count {
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.enabled-count {
|
||||
color: #67c23a;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.el-collapse-item__header) {
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
:deep(.el-collapse-item__content) {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
</style>
|
||||
143
web/src/view/ai/preset/components/PromptEditor.vue
Normal file
143
web/src/view/ai/preset/components/PromptEditor.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="编辑提示词" width="1000px" @close="handleClose">
|
||||
<div class="prompt-editor">
|
||||
<div class="toolbar">
|
||||
<el-button type="primary" size="small" @click="addPrompt">
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加提示词
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="prompts" style="width: 100%" max-height="500">
|
||||
<el-table-column label="启用" width="60" fixed>
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.enabled" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="标识符" prop="identifier" width="120" />
|
||||
<el-table-column label="名称" prop="name" width="150" />
|
||||
<el-table-column label="角色" width="100">
|
||||
<template #default="scope">
|
||||
<el-select v-model="scope.row.role" size="small">
|
||||
<el-option label="system" value="system" />
|
||||
<el-option label="user" value="user" />
|
||||
<el-option label="assistant" value="assistant" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="内容" min-width="200">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.content"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="提示词内容"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="系统提示" width="90">
|
||||
<template #default="scope">
|
||||
<el-checkbox v-model="scope.row.system_prompt" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="标记" width="70">
|
||||
<template #default="scope">
|
||||
<el-checkbox v-model="scope.row.marker" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="注入位置" width="100">
|
||||
<template #default="scope">
|
||||
<el-input-number v-model="scope.row.injection_position" :min="0" :max="1" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="注入深度" width="100">
|
||||
<template #default="scope">
|
||||
<el-input-number v-model="scope.row.injection_depth" :min="0" size="small" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="danger" link size="small" @click="removePrompt(scope.$index)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleSave">保存</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
prompts: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'save'])
|
||||
|
||||
const visible = ref(props.modelValue)
|
||||
const prompts = ref([])
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
visible.value = val
|
||||
if (val) {
|
||||
prompts.value = JSON.parse(JSON.stringify(props.prompts || []))
|
||||
}
|
||||
})
|
||||
|
||||
const addPrompt = () => {
|
||||
prompts.value.push({
|
||||
identifier: `prompt_${Date.now()}`,
|
||||
name: '新提示词',
|
||||
role: 'system',
|
||||
content: '',
|
||||
system_prompt: false,
|
||||
enabled: true,
|
||||
marker: false,
|
||||
injection_position: 0,
|
||||
injection_depth: 4,
|
||||
injection_order: 100,
|
||||
injection_trigger: [],
|
||||
forbid_overrides: false
|
||||
})
|
||||
}
|
||||
|
||||
const removePrompt = (index) => {
|
||||
prompts.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
emit('save', prompts.value)
|
||||
handleClose()
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.prompt-editor {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
||||
@@ -140,12 +140,36 @@
|
||||
:closable="false"
|
||||
style="margin-bottom: 20px"
|
||||
/>
|
||||
<el-input
|
||||
v-model="importJson"
|
||||
type="textarea"
|
||||
:rows="15"
|
||||
placeholder="请粘贴预设 JSON 内容"
|
||||
/>
|
||||
<el-tabs v-model="importTabActive">
|
||||
<el-tab-pane label="文件上传" name="file">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:auto-upload="false"
|
||||
:limit="1"
|
||||
accept=".json"
|
||||
drag
|
||||
:on-change="handleFileChange"
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
将 JSON 文件拖到此处,或<em>点击上传</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
只支持 .json 格式的预设文件
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="JSON 粘贴" name="json">
|
||||
<el-input
|
||||
v-model="importJson"
|
||||
type="textarea"
|
||||
:rows="15"
|
||||
placeholder="请粘贴预设 JSON 内容"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="importDialogVisible = false">取消</el-button>
|
||||
@@ -154,6 +178,13 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 提示词编辑器 -->
|
||||
<PromptEditor
|
||||
v-model="promptEditorVisible"
|
||||
:prompts="formData.prompts"
|
||||
@save="handlePromptSave"
|
||||
/>
|
||||
|
||||
<!-- 查看预设对话框 -->
|
||||
<el-dialog v-model="viewDialogVisible" title="预设详情" width="900px">
|
||||
<el-descriptions :column="2" border>
|
||||
@@ -212,13 +243,16 @@
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { UploadFilled } from '@element-plus/icons-vue'
|
||||
import {
|
||||
getAiPresetList,
|
||||
createAiPreset,
|
||||
updateAiPreset,
|
||||
deleteAiPreset,
|
||||
importAiPreset
|
||||
importAiPreset,
|
||||
importAiPresetFile
|
||||
} from '@/api/aiPreset'
|
||||
import PromptEditor from './components/PromptEditor.vue'
|
||||
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
@@ -233,6 +267,10 @@ const type = ref('')
|
||||
const presetForm = ref(null)
|
||||
const importJson = ref('')
|
||||
const viewData = ref({})
|
||||
const importTabActive = ref('file')
|
||||
const uploadRef = ref(null)
|
||||
const uploadFile = ref(null)
|
||||
const promptEditorVisible = ref(false)
|
||||
|
||||
const formData = ref({
|
||||
name: '',
|
||||
@@ -370,13 +408,34 @@ const deletePreset = (row) => {
|
||||
|
||||
const openImportDialog = () => {
|
||||
importJson.value = ''
|
||||
uploadFile.value = null
|
||||
importTabActive.value = 'file'
|
||||
importDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleFileChange = (file) => {
|
||||
uploadFile.value = file.raw
|
||||
}
|
||||
|
||||
const handleImport = async () => {
|
||||
try {
|
||||
const data = JSON.parse(importJson.value)
|
||||
const res = await importAiPreset(data)
|
||||
let res
|
||||
if (importTabActive.value === 'file') {
|
||||
// 文件上传导入
|
||||
if (!uploadFile.value) {
|
||||
ElMessage.warning('请选择要上传的文件')
|
||||
return
|
||||
}
|
||||
res = await importAiPresetFile(uploadFile.value)
|
||||
} else {
|
||||
// JSON 粘贴导入
|
||||
if (!importJson.value.trim()) {
|
||||
ElMessage.warning('请粘贴预设 JSON 内容')
|
||||
return
|
||||
}
|
||||
const data = JSON.parse(importJson.value)
|
||||
res = await importAiPreset(data)
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('导入成功')
|
||||
importDialogVisible.value = false
|
||||
@@ -400,7 +459,12 @@ const exportPreset = (row) => {
|
||||
}
|
||||
|
||||
const openPromptEditor = () => {
|
||||
ElMessage.info('提示词编辑器功能开发中...')
|
||||
promptEditorVisible.value = true
|
||||
}
|
||||
|
||||
const handlePromptSave = (prompts) => {
|
||||
formData.value.prompts = prompts
|
||||
ElMessage.success('提示词已更新')
|
||||
}
|
||||
|
||||
const openRegexEditor = () => {
|
||||
|
||||
@@ -32,6 +32,14 @@
|
||||
<el-table-column label="模型" prop="model" width="150" />
|
||||
<el-table-column label="优先级" prop="priority" width="80" />
|
||||
<el-table-column label="超时(秒)" prop="timeout" width="100" />
|
||||
<el-table-column label="默认" width="80">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.is_default" type="success">默认</el-tag>
|
||||
<el-button v-else type="primary" link size="small" @click="setDefault(scope.row)">
|
||||
设为默认
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="enabled" width="80">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
@@ -107,6 +115,9 @@
|
||||
<el-form-item label="启用" prop="enabled">
|
||||
<el-switch v-model="formData.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设为默认" prop="is_default">
|
||||
<el-switch v-model="formData.is_default" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
@@ -148,7 +159,8 @@ const formData = ref({
|
||||
priority: 0,
|
||||
timeout: 60,
|
||||
max_retries: 3,
|
||||
enabled: true
|
||||
enabled: true,
|
||||
is_default: false
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
@@ -237,7 +249,8 @@ const closeDialog = () => {
|
||||
priority: 0,
|
||||
timeout: 60,
|
||||
max_retries: 3,
|
||||
enabled: true
|
||||
enabled: true,
|
||||
is_default: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,6 +292,16 @@ const deleteProvider = (row) => {
|
||||
})
|
||||
}
|
||||
|
||||
const setDefault = async (row) => {
|
||||
const res = await updateAiProvider({ ...row, is_default: true })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('已设为默认提供商')
|
||||
getTableData()
|
||||
} else {
|
||||
ElMessage.error('设置失败')
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user