Merge branch 'main' of https://git.opencomputing.cn/yumoqing/kboss
This commit is contained in:
commit
52fb55fcd7
@ -121,4 +121,32 @@ export const reqTokenUsage = (params = {}) => {
|
||||
method: 'post',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 模型信息配置添加
|
||||
export const reqModelInfoConfig = (params = {}) => {
|
||||
return request({
|
||||
url: '/cntoai/model_management_add.dspy',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 模型信息配置编辑(编辑时需要额外传 id)
|
||||
export const reqModelInfoConfigEdit = (params = {}) => {
|
||||
return request({
|
||||
url: '/cntoai/model_management_update.dspy',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 模型信息配置列表
|
||||
export const reqModelInfoConfigList = (params = {}) => {
|
||||
return request({
|
||||
url: '/cntoai/model_management_search_doc.dspy',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
@ -464,6 +464,32 @@ export const asyncRoutes = [
|
||||
]
|
||||
},
|
||||
|
||||
// 运营——模型信息配置
|
||||
{
|
||||
path: "/modelInfoConfig",
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: "模型信息配置",
|
||||
fullPath: "/modelInfoConfig",
|
||||
noCache: true,
|
||||
icon: "el-icon-setting",
|
||||
roles: ["运营"]
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () => import('@/views/operation/modelInfoConfig/index.vue'),
|
||||
name: 'ModelInfoConfig',
|
||||
meta: {
|
||||
title: "模型信息配置",
|
||||
fullPath: "/modelInfoConfig",
|
||||
noCache: true,
|
||||
roles: ["运营"]
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
// token市集 - 一级菜单(所有登录用户都能看到)
|
||||
{
|
||||
|
||||
@ -17,7 +17,7 @@ const SUPER_ADMIN_ROUTE_PATH = '/superAdministrator';
|
||||
const COMMON_ROUTE_PATHS = ['/product', '/tokenManagement', '/tokenUsage', '/modelExperience', '/modelDetail', '/modelApiDocument'];
|
||||
|
||||
// 运营角色需要额外补出来的菜单。
|
||||
const OPERATION_EXTRA_ROUTE_PATHS = ['/modelManagement', '/operationReport'];
|
||||
const OPERATION_EXTRA_ROUTE_PATHS = ['/modelManagement', '/modelInfoConfig', '/operationReport'];
|
||||
|
||||
// 普通客户账号默认要补出来的基础菜单。
|
||||
const BASE_USER_ROUTE_PATHS = ['/orderManagement', '/resourceManagement'];
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
</li>
|
||||
<li v-if="JSON.stringify(logoInfoNew)!=='{}'"> 邮箱:{{logoInfoNew.home.email}}</li>
|
||||
<!-- <li v-if="JSON.stringify(logoInfoNew)!=='{}'">电话: <span class="tel">{{logoInfoNew.home.mobile}}</span> -->
|
||||
<!-- </li> -->
|
||||
<!-- </li> -->
|
||||
|
||||
<li>
|
||||
<!-- <a href="" rel="noreferrer" target="_blank"></a> -->
|
||||
|
||||
@ -0,0 +1,323 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="模型信息配置详情"
|
||||
:visible.sync="dialogVisible"
|
||||
width="680px"
|
||||
class="model-detail-dialog"
|
||||
append-to-body
|
||||
>
|
||||
<div class="detail-content">
|
||||
<div class="detail-grid">
|
||||
<div class="detail-item">
|
||||
<span>模型ID</span>
|
||||
<strong>{{ displayValue(detail.id) }}</strong>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span>模型名称/版本</span>
|
||||
<strong>{{ displayValue(detail.model_name || detail.display_name) }}</strong>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span>展示名称</span>
|
||||
<strong>{{ displayValue(detail.display_name) }}</strong>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span>模型类型</span>
|
||||
<strong>{{ displayValue(detail.model_type) }}</strong>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span>供应商</span>
|
||||
<strong>{{ displayValue(detail.provider) }}</strong>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span>接入ID</span>
|
||||
<strong>{{ displayValue(detail.llmid) }}</strong>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span>上下文长度</span>
|
||||
<strong>{{ displayValue(detail.context_length) }}</strong>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span>输入token单价</span>
|
||||
<strong>{{ displayValue(detail.input_token_price) }}</strong>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span>输出token单价</span>
|
||||
<strong>{{ displayValue(detail.output_token_price) }}</strong>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span>输入缓存命中token单价</span>
|
||||
<strong>{{ displayValue(detail.cache_hit_input_price) }}</strong>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span>计费方式</span>
|
||||
<strong>{{ displayValue(detail.billing_method) }}</strong>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span>计费单位</span>
|
||||
<strong>{{ displayValue(detail.billing_unit) }}</strong>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span>是否支持体验</span>
|
||||
<el-tag size="mini" :type="Number(detail.experience) === 1 ? 'success' : 'info'">
|
||||
{{ Number(detail.experience) === 1 ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div
|
||||
v-for="item in capabilityList"
|
||||
:key="`capability-${item.name}`"
|
||||
class="detail-item"
|
||||
>
|
||||
<span>{{ item.name }}</span>
|
||||
<el-tag v-if="isSupportValue(item.value)" size="mini" type="success">{{ item.value }}</el-tag>
|
||||
<strong v-else>{{ displayValue(item.value) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="detail-section">
|
||||
<h4>模型限制</h4>
|
||||
<div class="detail-grid">
|
||||
<div v-for="item in limitationList" :key="`limit-${item.name}`" class="detail-item">
|
||||
<span>{{ item.name }}</span>
|
||||
<strong>{{ displayValue(item.value) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="detail-section">
|
||||
<h4>模型亮点</h4>
|
||||
<div class="highlight-list">
|
||||
<el-tag
|
||||
v-for="item in highlightList"
|
||||
:key="`highlight-${item.name}-${item.value}`"
|
||||
size="mini"
|
||||
type="primary"
|
||||
effect="plain"
|
||||
>
|
||||
{{ item.value || item.name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="detail-section">
|
||||
<div class="detail-item block">
|
||||
<span>状态</span>
|
||||
<el-tag size="mini" :type="detail.listing_status === 1 ? 'success' : 'warning'">
|
||||
{{ detail.listing_status === 1 ? '已上架' : '待上架' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="detail-item block">
|
||||
<span>模型简介</span>
|
||||
<p>{{ displayValue(detail.description) }}</p>
|
||||
</div>
|
||||
<div class="detail-item block">
|
||||
<span>接口地址</span>
|
||||
<a>{{ displayValue(apiDoc.api_url || detail.api_url) }}</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="detail-section">
|
||||
<h4>curl代码</h4>
|
||||
<pre class="code-block">{{ displayValue(apiDoc.curl_code || detail.curl_code) }}</pre>
|
||||
</section>
|
||||
|
||||
<section class="detail-section">
|
||||
<h4>Python代码</h4>
|
||||
<pre class="code-block">{{ displayValue(apiDoc.python_code || detail.python_code) }}</pre>
|
||||
</section>
|
||||
|
||||
<section class="detail-section">
|
||||
<h4>错误码</h4>
|
||||
<el-table :data="errorCodes" size="mini" border class="error-table">
|
||||
<el-table-column prop="code" label="错误码" width="110"></el-table-column>
|
||||
<el-table-column prop="message" label="说明"></el-table-column>
|
||||
<el-table-column prop="solution" label="处理方式"></el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<div class="detail-item block">
|
||||
<span>更新时间</span>
|
||||
<strong>{{ displayValue(detail.updated_at || detail.updatedAt) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ModelInfoDetailDialog',
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
detail: {},
|
||||
errorCodes: [
|
||||
{ code: '200', message: '请求成功', solution: '请参考文档' },
|
||||
{ code: '400', message: '请求参数错误', solution: '请参考文档' },
|
||||
{ code: '401', message: '认证失败', solution: '请参考文档' },
|
||||
{ code: '429', message: '请求频率超限', solution: '请参考文档' },
|
||||
{ code: '500', message: '服务器内部错误', solution: '请参考文档' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
apiDoc() {
|
||||
const parsed = this.safeParse(this.detail.api_doc)
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
||||
},
|
||||
capabilityList() {
|
||||
return this.normalizeNameValueList(this.detail.capabilities, [
|
||||
{ name: '接口类型', value: '-' },
|
||||
{ name: '接入ID', value: this.detail.llmid || '-' },
|
||||
{ name: '输入类型', value: '-' },
|
||||
{ name: '输出类型', value: '-' },
|
||||
{ name: 'Function Call', value: '-' },
|
||||
{ name: '联网搜索', value: '-' },
|
||||
{ name: '私有化部署', value: '-' }
|
||||
])
|
||||
},
|
||||
limitationList() {
|
||||
return this.normalizeNameValueList(this.detail.limitations, [
|
||||
{ name: '输入长度', value: this.detail.context_length || '-' },
|
||||
{ name: '单轮输出', value: '-' },
|
||||
{ name: '输出长度', value: '-' },
|
||||
{ name: '服务速度限制', value: '-' }
|
||||
])
|
||||
},
|
||||
highlightList() {
|
||||
return this.normalizeNameValueList(this.detail.highlights, [])
|
||||
},
|
||||
requestExampleText() {
|
||||
return this.apiDoc.curl_code || this.detail.curl_code || '-'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open(row = {}) {
|
||||
this.detail = row || {}
|
||||
this.dialogVisible = true
|
||||
},
|
||||
normalizeNameValueList(value, fallback = []) {
|
||||
const parsed = this.safeParse(value)
|
||||
if (Array.isArray(parsed)) {
|
||||
const list = parsed.map(item => ({
|
||||
name: this.displayValue(item.name || item.label || item.key),
|
||||
value: this.displayValue(item.value || item.text || item.content)
|
||||
})).filter(item => item.name !== '-' || item.value !== '-')
|
||||
return list.length ? list : fallback
|
||||
}
|
||||
if (parsed && typeof parsed === 'object') {
|
||||
return Object.keys(parsed).map(key => ({
|
||||
name: key,
|
||||
value: this.displayValue(parsed[key])
|
||||
}))
|
||||
}
|
||||
return fallback
|
||||
},
|
||||
safeParse(value) {
|
||||
if (!value || typeof value !== 'string') return value
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (error) {
|
||||
return value
|
||||
}
|
||||
},
|
||||
displayValue(value) {
|
||||
if (value === undefined || value === null || value === '') return '-'
|
||||
if (typeof value === 'object') return JSON.stringify(value)
|
||||
return String(value)
|
||||
},
|
||||
isSupportValue(value) {
|
||||
return ['支持', '不支持', '是', '否'].includes(String(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.model-detail-dialog {
|
||||
/deep/ .el-dialog {
|
||||
border-radius: 12px;
|
||||
margin-top: 6vh !important;
|
||||
}
|
||||
|
||||
/deep/ .el-dialog__body {
|
||||
max-height: 78vh;
|
||||
padding: 18px 22px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
color: #1f2d3d;
|
||||
}
|
||||
|
||||
.detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 18px 48px;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
span {
|
||||
display: block;
|
||||
margin-bottom: 7px;
|
||||
color: #98a2b3;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
strong,
|
||||
p,
|
||||
a {
|
||||
margin: 0;
|
||||
color: #344054;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 1.7;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #1e6fff;
|
||||
}
|
||||
|
||||
&.block {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
padding-top: 18px;
|
||||
margin-top: 18px;
|
||||
border-top: 1px solid #f0f2f5;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 14px;
|
||||
color: #1f2d3d;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.highlight-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
padding: 14px;
|
||||
margin: 0;
|
||||
color: #475467;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
background: #f8fafc;
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.error-table {
|
||||
/deep/ th {
|
||||
background: #f8fafc;
|
||||
color: #667085;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,899 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="editForm.id ? '编辑模型信息' : '添加模型信息'"
|
||||
:visible.sync="dialogVisible"
|
||||
width="680px"
|
||||
class="model-edit-dialog"
|
||||
append-to-body
|
||||
>
|
||||
<el-form ref="editForm" :model="editForm" label-position="top" class="edit-form">
|
||||
<div class="form-section">
|
||||
<div class="section-title">智能识别</div>
|
||||
<div class="ai-recognition-row">
|
||||
<el-input
|
||||
v-model="editForm.aiText"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="可粘贴模型介绍、API文档等内容,自动识别并填充下方表单..."
|
||||
></el-input>
|
||||
<el-button type="primary" size="small" icon="el-icon-magic-stick" @click="smartRecognize">识别</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<div class="section-title">基础信息</div>
|
||||
<div class="form-grid">
|
||||
<el-form-item label="厂商名称">
|
||||
<el-input v-model="editForm.provider" size="small" placeholder="如:DeepSeek"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="模型名称">
|
||||
<el-input v-model="editForm.modelName" size="small" placeholder="如:DeepSeek3"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="展示名称">
|
||||
<el-input v-model="editForm.displayName" size="small" placeholder="如:DeepSeek3"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="上下文长度">
|
||||
<el-input v-model="editForm.contextLength" size="small" placeholder="如:256K"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="模型类型">
|
||||
<el-select v-model="editForm.type" size="small" placeholder="请选择模型类型">
|
||||
<el-option label="对话" value="对话"></el-option>
|
||||
<el-option label="文本生成" value="文本生成"></el-option>
|
||||
<el-option label="图像生成" value="图像生成"></el-option>
|
||||
<el-option label="语音合成" value="语音合成"></el-option>
|
||||
<el-option label="多模态" value="多模态"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="供应商LOGO">
|
||||
<div class="logo-upload">
|
||||
<el-button size="small" icon="el-icon-upload2">选择文件</el-button>
|
||||
<span>{{ editForm.logo || '未选择任何文件' }}</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="接口地址" class="span-2">
|
||||
<el-input v-model="editForm.apiUrl" size="small" placeholder="请输入接口地址"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" class="span-2">
|
||||
<el-input
|
||||
v-model="editForm.description"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入模型介绍"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="是否支持体验">
|
||||
<el-radio-group v-model="editForm.experience">
|
||||
<el-radio :label="1">是</el-radio>
|
||||
<el-radio :label="0">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<div class="section-title">价格与计费</div>
|
||||
<div class="form-grid">
|
||||
<el-form-item label="输入token单价">
|
||||
<el-input v-model="editForm.inputTokenPrice" size="small" placeholder="如:0.00011"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="输出token单价">
|
||||
<el-input v-model="editForm.outputTokenPrice" size="small" placeholder="如:0.00012"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="输入缓存命中token单价">
|
||||
<el-input v-model="editForm.cacheHitInputPrice" size="small" placeholder="如:0.000056"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="计费方式">
|
||||
<el-input v-model="editForm.billingMethod" size="small" placeholder="如:token"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="计费单位">
|
||||
<el-input v-model="editForm.billingUnit" size="small" placeholder="如:tokens"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<div class="section-title">模型能力</div>
|
||||
<div class="form-grid">
|
||||
<el-form-item label="接口类型">
|
||||
<el-input v-model="editForm.apiType" size="small" placeholder="如:对话补全接口"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="输入能力">
|
||||
<el-input v-model="editForm.inputCapability" size="small" placeholder="如:文本"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="输入类型">
|
||||
<el-select v-model="editForm.inputType" size="small" placeholder="请选择输入类型">
|
||||
<el-option label="文本" value="文本"></el-option>
|
||||
<el-option label="图片" value="图片"></el-option>
|
||||
<el-option label="音频" value="音频"></el-option>
|
||||
<el-option label="多模态" value="多模态"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="输出类型">
|
||||
<el-select v-model="editForm.outputType" size="small" placeholder="请选择输出类型">
|
||||
<el-option label="文本" value="文本"></el-option>
|
||||
<el-option label="图片" value="图片"></el-option>
|
||||
<el-option label="音频" value="音频"></el-option>
|
||||
<el-option label="多模态" value="多模态"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="Function Call">
|
||||
<el-radio-group v-model="editForm.functionCall">
|
||||
<el-radio label="支持">支持</el-radio>
|
||||
<el-radio label="不支持">不支持</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="联网搜索">
|
||||
<el-radio-group v-model="editForm.webSearch">
|
||||
<el-radio label="支持">支持</el-radio>
|
||||
<el-radio label="不支持">不支持</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="私有化部署">
|
||||
<el-radio-group v-model="editForm.privateDeploy">
|
||||
<el-radio label="支持">支持</el-radio>
|
||||
<el-radio label="不支持">不支持</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<div class="section-title section-between">
|
||||
<span>模型亮点</span>
|
||||
<el-button type="text" size="mini" icon="el-icon-plus" @click="addHighlight">添加亮点</el-button>
|
||||
</div>
|
||||
<div class="dynamic-list">
|
||||
<div v-for="(item, index) in editForm.highlights" :key="index" class="dynamic-row">
|
||||
<span class="row-index">{{ index + 1 }}</span>
|
||||
<el-input v-model="item.name" size="small" placeholder="亮点名称"></el-input>
|
||||
<el-input v-model="item.value" size="small" placeholder="亮点描述"></el-input>
|
||||
<i class="el-icon-close" @click="removeHighlight(index)"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<div class="section-title section-between">
|
||||
<span>模型限制</span>
|
||||
<el-button type="text" size="mini" icon="el-icon-plus" @click="addLimit">添加限制</el-button>
|
||||
</div>
|
||||
<div class="dynamic-list">
|
||||
<div v-for="(item, index) in editForm.limitations" :key="index" class="dynamic-row">
|
||||
<span class="row-index">{{ index + 1 }}</span>
|
||||
<el-input v-model="item.name" size="small" placeholder="限制名称"></el-input>
|
||||
<el-input v-model="item.value" size="small" placeholder="限制说明"></el-input>
|
||||
<i class="el-icon-close" @click="removeLimit(index)"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<div class="section-title">API 文档信息</div>
|
||||
<div class="form-grid">
|
||||
<!-- <el-form-item label="认证方式">
|
||||
<el-input v-model="editForm.authType" size="small" placeholder="如:对话补全接口"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="接口名称">
|
||||
<el-input v-model="editForm.endpointName" size="small" placeholder="如:对话补全接口"></el-input>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="接口地址" class="span-2">
|
||||
<el-input v-model="editForm.requestUrl" size="small" placeholder="请输入请求地址"></el-input>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="请求方式">
|
||||
<el-select v-model="editForm.method" size="small" placeholder="请选择请求方式">
|
||||
<el-option label="POST" value="POST"></el-option>
|
||||
<el-option label="GET" value="GET"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="模型字段">
|
||||
<el-input v-model="editForm.modelField" size="small" placeholder="如:chat_completion_v1"></el-input>
|
||||
</el-form-item> -->
|
||||
<!-- <el-form-item label="请求头" class="span-2">
|
||||
<el-input
|
||||
v-model="editForm.headers"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder='如:{"Authorization":"Bearer sk-xxx","Content-Type":"application/json"}'
|
||||
></el-input>
|
||||
</el-form-item> -->
|
||||
<el-form-item label="curl代码" class="span-2">
|
||||
<el-input
|
||||
v-model="editForm.curlCode"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入curl代码"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Python代码" class="span-2">
|
||||
<el-input
|
||||
v-model="editForm.pythonCode"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入Python代码"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="请求示例" class="span-2">
|
||||
<div class="example-card-list">
|
||||
<div class="section-between example-title-row">
|
||||
<span></span>
|
||||
<el-button type="text" size="mini" icon="el-icon-plus" @click="addExample">添加示例</el-button>
|
||||
</div>
|
||||
<div v-for="(item, index) in editForm.examples" :key="index" class="example-card">
|
||||
<i v-if="editForm.examples.length > 1" class="el-icon-close example-remove" @click="removeExample(index)"></i>
|
||||
<div class="example-field">
|
||||
<label>示例标识</label>
|
||||
<el-input v-model="item.tag" size="small" placeholder="如:chat_completion_v1"></el-input>
|
||||
</div>
|
||||
<div class="example-field">
|
||||
<label>请求示例</label>
|
||||
<el-input
|
||||
v-model="item.content"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入请求示例"
|
||||
></el-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item> -->
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="dialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="submitLoading" @click="handleSubmit">保存</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reqModelInfoConfig, reqModelInfoConfigEdit } from '@/api/model/model'
|
||||
|
||||
const DEFAULT_EDIT_FORM = {
|
||||
id: '',
|
||||
aiText: '',
|
||||
name: '',
|
||||
modelName: '',
|
||||
displayName: '',
|
||||
type: '',
|
||||
provider: '',
|
||||
logo: '',
|
||||
apiUrl: '',
|
||||
description: '',
|
||||
contextLength: '',
|
||||
versionDescription: '',
|
||||
experience: 1,
|
||||
inputTokenPrice: '',
|
||||
outputTokenPrice: '',
|
||||
cacheHitInputPrice: '',
|
||||
billingMethod: '',
|
||||
billingUnit: '',
|
||||
apiType: '',
|
||||
inputCapability: '',
|
||||
inputType: '',
|
||||
outputType: '',
|
||||
functionCall: '支持',
|
||||
webSearch: '不支持',
|
||||
privateDeploy: '不支持',
|
||||
highlights: [
|
||||
{ name: '', value: '' },
|
||||
],
|
||||
limitations: [
|
||||
{ name: '', value: '' },
|
||||
],
|
||||
authType: '',
|
||||
endpointName: '',
|
||||
requestUrl: '',
|
||||
method: '',
|
||||
modelField: '',
|
||||
headers: '',
|
||||
curlCode: '',
|
||||
pythonCode: '',
|
||||
examples: [
|
||||
{
|
||||
tag: '',
|
||||
content: ''
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'ModelInfoEditDialog',
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
submitLoading: false,
|
||||
editForm: this.createDefaultForm()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
createDefaultForm() {
|
||||
return JSON.parse(JSON.stringify(DEFAULT_EDIT_FORM))
|
||||
},
|
||||
open(row = {}) {
|
||||
const defaultForm = this.createDefaultForm()
|
||||
const apiDoc = this.normalizeApiDoc(row.api_doc)
|
||||
const capabilities = this.normalizeNameValueList(row.capabilities)
|
||||
const limitations = this.normalizeNameValueList(row.limitations)
|
||||
const highlights = this.normalizeNameValueList(row.highlights)
|
||||
const apiUrl = this.toFormValue(apiDoc.api_url || row.api_url || '')
|
||||
const curlCode = this.toTextareaValue(apiDoc.curl_code || row.curl_code || '')
|
||||
const pythonCode = this.toTextareaValue(apiDoc.python_code || row.python_code || '')
|
||||
|
||||
this.editForm = {
|
||||
...defaultForm,
|
||||
id: row.id || '',
|
||||
name: row.model_name || row.name || '',
|
||||
modelName: row.model_name || row.name || '',
|
||||
displayName: row.display_name || row.displayName || row.model_name || row.name || '',
|
||||
type: row.model_type || row.type || '',
|
||||
provider: row.provider || '',
|
||||
logo: row.provider_logo || row.logo || '',
|
||||
apiUrl,
|
||||
requestUrl: apiUrl,
|
||||
description: row.description || '',
|
||||
contextLength: this.toFormValue(row.context_length),
|
||||
versionDescription: row.version_description || row.versionDescription || row.display_name || row.provider || '',
|
||||
experience: Number(row.experience) === 0 ? 0 : 1,
|
||||
inputTokenPrice: this.toFormValue(row.input_token_price),
|
||||
outputTokenPrice: this.toFormValue(row.output_token_price),
|
||||
cacheHitInputPrice: this.toFormValue(row.cache_hit_input_price),
|
||||
billingMethod: row.billing_method || '',
|
||||
billingUnit: row.billing_unit || '',
|
||||
curlCode,
|
||||
pythonCode,
|
||||
examples: this.normalizeExamples(apiDoc, row, defaultForm.examples),
|
||||
limitations: limitations.length ? limitations : defaultForm.limitations,
|
||||
highlights: highlights.length ? highlights : defaultForm.highlights
|
||||
}
|
||||
|
||||
this.applyCapabilityList(capabilities)
|
||||
this.dialogVisible = true
|
||||
},
|
||||
addHighlight() {
|
||||
this.editForm.highlights.push({ name: '', value: '' })
|
||||
},
|
||||
removeHighlight(index) {
|
||||
this.editForm.highlights.splice(index, 1)
|
||||
},
|
||||
addLimit() {
|
||||
this.editForm.limitations.push({ name: '', value: '' })
|
||||
},
|
||||
removeLimit(index) {
|
||||
this.editForm.limitations.splice(index, 1)
|
||||
},
|
||||
addExample() {
|
||||
this.editForm.examples.push({ tag: '', content: '' })
|
||||
},
|
||||
removeExample(index) {
|
||||
this.editForm.examples.splice(index, 1)
|
||||
},
|
||||
async handleSubmit() {
|
||||
const params = this.buildSubmitParams()
|
||||
const isEdit = Boolean(this.editForm.id)
|
||||
this.submitLoading = true
|
||||
try {
|
||||
const res = isEdit
|
||||
? await reqModelInfoConfigEdit({ ...params, id: this.editForm.id })
|
||||
: await reqModelInfoConfig(params)
|
||||
if (res && res.status) {
|
||||
this.$message.success(res.msg || (isEdit ? '模型信息编辑成功' : '模型信息添加成功'))
|
||||
this.dialogVisible = false
|
||||
this.$emit('success')
|
||||
return
|
||||
}
|
||||
this.$message.error((res && res.msg) || (isEdit ? '模型信息编辑失败' : '模型信息添加失败'))
|
||||
} catch (error) {
|
||||
this.$message.error(isEdit ? '模型信息编辑失败' : '模型信息添加失败')
|
||||
} finally {
|
||||
this.submitLoading = false
|
||||
}
|
||||
},
|
||||
buildSubmitParams() {
|
||||
return {
|
||||
provider: this.editForm.provider,
|
||||
model_name: this.editForm.modelName,
|
||||
display_name: this.editForm.displayName,
|
||||
context_length: this.editForm.contextLength,
|
||||
model_type: this.editForm.type,
|
||||
input_token_price: this.editForm.inputTokenPrice,
|
||||
output_token_price: this.editForm.outputTokenPrice,
|
||||
cache_hit_input_price: this.editForm.cacheHitInputPrice,
|
||||
billing_method: this.editForm.billingMethod,
|
||||
billing_unit: this.editForm.billingUnit,
|
||||
capabilities: JSON.stringify(this.buildCapabilities()),
|
||||
limitations: JSON.stringify(this.filterNameValueList(this.editForm.limitations)),
|
||||
highlights: JSON.stringify(this.filterNameValueList(this.editForm.highlights)),
|
||||
description: this.editForm.description,
|
||||
experience: this.editForm.experience,
|
||||
api_url: this.editForm.apiUrl || this.editForm.requestUrl,
|
||||
curl_code: this.editForm.curlCode,
|
||||
python_code: this.editForm.pythonCode
|
||||
}
|
||||
},
|
||||
buildCapabilities() {
|
||||
return [
|
||||
{ name: '接口类型', value: this.editForm.apiType },
|
||||
{ name: '接入ID', value: this.editForm.inputCapability },
|
||||
{ name: '输入类型', value: this.editForm.inputType },
|
||||
{ name: '输出类型', value: this.editForm.outputType },
|
||||
{ name: 'Function Call', value: this.editForm.functionCall },
|
||||
{ name: '联网搜索', value: this.editForm.webSearch },
|
||||
{ name: '私有化部署', value: this.editForm.privateDeploy }
|
||||
].filter(item => item.name && item.value)
|
||||
},
|
||||
filterNameValueList(list) {
|
||||
return (Array.isArray(list) ? list : []).filter(item => item.name || item.value)
|
||||
},
|
||||
normalizeApiDoc(value) {
|
||||
const parsed = this.parseJsonValue(value)
|
||||
if (Array.isArray(parsed)) {
|
||||
return parsed.reduce((result, item, index) => {
|
||||
if (item && typeof item === 'object') {
|
||||
const key = item.name || item.label || item.key || item.title || `item_${index}`
|
||||
result[key] = item.value || item.content || item.text || item.example || item
|
||||
}
|
||||
return result
|
||||
}, {})
|
||||
}
|
||||
return parsed && typeof parsed === 'object' ? parsed : {}
|
||||
},
|
||||
normalizeExamples(apiDoc = {}, row = {}, fallback = []) {
|
||||
const rawExamples = this.getApiDocValue(apiDoc, row, ['examples', 'request_examples', 'requestExamples', '请求示例'])
|
||||
const parsedExamples = this.parseJsonValue(rawExamples)
|
||||
if (Array.isArray(parsedExamples) && parsedExamples.length) {
|
||||
return parsedExamples.map(item => ({
|
||||
tag: this.toDisplayText(item.tag || item.name || item.label || item.key),
|
||||
content: this.toDisplayText(item.content || item.value || item.example || item.text)
|
||||
})).filter(item => item.tag || item.content)
|
||||
}
|
||||
|
||||
const requestExample = this.getApiDocValue(apiDoc, row, ['request_example', 'requestExample', 'example', '请求示例'])
|
||||
if (requestExample) {
|
||||
return [{
|
||||
tag: this.getApiDocValue(apiDoc, row, ['model_field', 'modelField', '模型字段']) || 'chat_completion_v1',
|
||||
content: this.toDisplayText(requestExample)
|
||||
}]
|
||||
}
|
||||
|
||||
return fallback
|
||||
},
|
||||
getApiDocValue(apiDoc = {}, row = {}, keys = []) {
|
||||
const value = this.findValueByKeys(apiDoc, keys)
|
||||
if (value !== undefined && value !== null && value !== '') return value
|
||||
return this.findValueByKeys(row, keys)
|
||||
},
|
||||
findValueByKeys(source, keys = []) {
|
||||
if (!source || typeof source !== 'object') return ''
|
||||
const normalizedKeys = keys.map(key => this.normalizeFieldKey(key))
|
||||
|
||||
if (Array.isArray(source)) {
|
||||
for (const item of source) {
|
||||
if (!item || typeof item !== 'object') continue
|
||||
const itemKey = item.name || item.label || item.key || item.title
|
||||
if (itemKey && normalizedKeys.includes(this.normalizeFieldKey(itemKey))) {
|
||||
return item.value || item.content || item.text || item.example || ''
|
||||
}
|
||||
const childValue = this.findValueByKeys(item, keys)
|
||||
if (childValue !== undefined && childValue !== null && childValue !== '') return childValue
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const directKey = Object.keys(source).find(key => normalizedKeys.includes(this.normalizeFieldKey(key)))
|
||||
if (directKey) return source[directKey]
|
||||
|
||||
for (const key of Object.keys(source)) {
|
||||
const child = this.parseJsonValue(source[key])
|
||||
if (!child || typeof child !== 'object') continue
|
||||
const childValue = this.findValueByKeys(child, keys)
|
||||
if (childValue !== undefined && childValue !== null && childValue !== '') return childValue
|
||||
}
|
||||
|
||||
return ''
|
||||
},
|
||||
normalizeFieldKey(key) {
|
||||
return String(key || '').replace(/[\s_\-::]/g, '').toLowerCase()
|
||||
},
|
||||
toFormValue(value) {
|
||||
if (value === undefined || value === null) return ''
|
||||
return String(value)
|
||||
},
|
||||
toTextareaValue(value) {
|
||||
if (!value) return ''
|
||||
if (typeof value === 'object') return JSON.stringify(value)
|
||||
return String(value)
|
||||
},
|
||||
smartRecognize() {
|
||||
const sourceText = String(this.editForm.aiText || '').trim()
|
||||
if (!sourceText) {
|
||||
this.$message.warning('请先粘贴需要识别的内容')
|
||||
return
|
||||
}
|
||||
|
||||
const recognizedData = this.extractRecognizedData(sourceText)
|
||||
if (!Object.keys(recognizedData).length) {
|
||||
this.$message.warning('未识别到可填充的模型信息')
|
||||
return
|
||||
}
|
||||
|
||||
this.applyRecognizedData(recognizedData)
|
||||
this.$message.success('识别完成,已自动填充表单')
|
||||
},
|
||||
extractRecognizedData(text) {
|
||||
const parsedObject = this.parsePossibleJson(text)
|
||||
if (parsedObject && typeof parsedObject === 'object') {
|
||||
return parsedObject.data && typeof parsedObject.data === 'object' ? parsedObject.data : parsedObject
|
||||
}
|
||||
|
||||
const fieldKeys = [
|
||||
'provider',
|
||||
'model_name',
|
||||
'display_name',
|
||||
'context_length',
|
||||
'model_type',
|
||||
'input_token_price',
|
||||
'output_token_price',
|
||||
'cache_hit_input_price',
|
||||
'billing_method',
|
||||
'billing_unit',
|
||||
'capabilities',
|
||||
'limitations',
|
||||
'highlights',
|
||||
'description',
|
||||
'api_url',
|
||||
'curl_code',
|
||||
'python_code'
|
||||
]
|
||||
return fieldKeys.reduce((result, key) => {
|
||||
const value = this.extractFieldValue(text, key)
|
||||
if (value !== '') {
|
||||
result[key] = value
|
||||
}
|
||||
return result
|
||||
}, {})
|
||||
},
|
||||
parsePossibleJson(text) {
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch (error) {
|
||||
const match = text.match(/\{[\s\S]*\}/)
|
||||
if (!match) return null
|
||||
try {
|
||||
return JSON.parse(match[0])
|
||||
} catch (innerError) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
},
|
||||
extractFieldValue(text, key) {
|
||||
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
const lineRegExp = new RegExp(`(?:^|\\n)\\s*${escapedKey}\\s*(?:=|:)?\\s*([^\\n\\r]+)`, 'i')
|
||||
const lineMatch = text.match(lineRegExp)
|
||||
if (lineMatch && lineMatch[1]) {
|
||||
return lineMatch[1].replace(/^["']|["']$/g, '').trim()
|
||||
}
|
||||
|
||||
const tableRegExp = new RegExp(`(?:^|\\n)\\s*${escapedKey}\\s*[=\\s]+([^\\n\\r]+?)(?:\\s+string\\b|\\s+number\\b|\\s+int\\b|\\s+float\\b|\\s+boolean\\b|$)`, 'i')
|
||||
const tableMatch = text.match(tableRegExp)
|
||||
if (tableMatch && tableMatch[1]) {
|
||||
return tableMatch[1].replace(/^["']|["']$/g, '').trim()
|
||||
}
|
||||
|
||||
return ''
|
||||
},
|
||||
applyRecognizedData(data) {
|
||||
const capabilities = this.normalizeNameValueList(data.capabilities)
|
||||
const limitations = this.normalizeNameValueList(data.limitations)
|
||||
const highlights = this.normalizeNameValueList(data.highlights)
|
||||
|
||||
this.editForm = {
|
||||
...this.editForm,
|
||||
provider: this.pickRecognizedValue(data.provider, this.editForm.provider),
|
||||
modelName: this.pickRecognizedValue(data.model_name, this.editForm.modelName),
|
||||
name: this.pickRecognizedValue(data.model_name, this.editForm.name),
|
||||
displayName: this.pickRecognizedValue(data.display_name, this.editForm.displayName),
|
||||
contextLength: this.pickRecognizedValue(data.context_length, this.editForm.contextLength),
|
||||
type: this.pickRecognizedValue(data.model_type, this.editForm.type),
|
||||
inputTokenPrice: this.pickRecognizedValue(data.input_token_price, this.editForm.inputTokenPrice),
|
||||
outputTokenPrice: this.pickRecognizedValue(data.output_token_price, this.editForm.outputTokenPrice),
|
||||
cacheHitInputPrice: this.pickRecognizedValue(data.cache_hit_input_price, this.editForm.cacheHitInputPrice),
|
||||
billingMethod: this.pickRecognizedValue(data.billing_method, this.editForm.billingMethod),
|
||||
billingUnit: this.pickRecognizedValue(data.billing_unit, this.editForm.billingUnit),
|
||||
description: this.pickRecognizedValue(data.description, this.editForm.description),
|
||||
versionDescription: this.pickRecognizedValue(data.description, this.editForm.versionDescription),
|
||||
apiUrl: this.pickRecognizedValue(data.api_url, this.editForm.apiUrl),
|
||||
requestUrl: this.pickRecognizedValue(data.api_url, this.editForm.requestUrl),
|
||||
curlCode: this.pickRecognizedValue(data.curl_code, this.editForm.curlCode),
|
||||
pythonCode: this.pickRecognizedValue(data.python_code, this.editForm.pythonCode),
|
||||
limitations: limitations.length ? limitations : this.editForm.limitations,
|
||||
highlights: highlights.length ? highlights : this.editForm.highlights
|
||||
}
|
||||
|
||||
this.applyCapabilityList(capabilities)
|
||||
if (data.curl_code) {
|
||||
this.syncExampleFromCurl(String(data.curl_code))
|
||||
}
|
||||
},
|
||||
normalizeNameValueList(value) {
|
||||
const parsedValue = this.parseJsonValue(value)
|
||||
if (Array.isArray(parsedValue)) {
|
||||
return parsedValue.map(item => ({
|
||||
name: this.toDisplayText(item.name || item.label || item.key),
|
||||
value: this.toDisplayText(item.value || item.text || item.content)
|
||||
})).filter(item => item.name || item.value)
|
||||
}
|
||||
|
||||
if (parsedValue && typeof parsedValue === 'object') {
|
||||
return Object.keys(parsedValue).map(key => ({
|
||||
name: key,
|
||||
value: this.toDisplayText(parsedValue[key])
|
||||
}))
|
||||
}
|
||||
|
||||
return []
|
||||
},
|
||||
parseJsonValue(value) {
|
||||
if (!value) return value
|
||||
if (typeof value !== 'string') return value
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (error) {
|
||||
return value
|
||||
}
|
||||
},
|
||||
applyCapabilityList(capabilities) {
|
||||
if (!capabilities.length) return
|
||||
|
||||
const getValueByName = (...names) => {
|
||||
const match = capabilities.find(item => names.includes(item.name))
|
||||
return match ? match.value : ''
|
||||
}
|
||||
|
||||
this.editForm.apiType = this.pickRecognizedValue(getValueByName('接口类型', 'api_type'), this.editForm.apiType)
|
||||
this.editForm.inputCapability = this.pickRecognizedValue(getValueByName('输入能力', '接入ID', 'input_capability'), this.editForm.inputCapability)
|
||||
this.editForm.inputType = this.pickRecognizedValue(getValueByName('输入类型', 'input_type'), this.editForm.inputType)
|
||||
this.editForm.outputType = this.pickRecognizedValue(getValueByName('输出类型', 'output_type'), this.editForm.outputType)
|
||||
this.editForm.functionCall = this.pickRecognizedValue(getValueByName('Function Call', 'function_call'), this.editForm.functionCall)
|
||||
this.editForm.webSearch = this.pickRecognizedValue(getValueByName('联网搜索', 'web_search'), this.editForm.webSearch)
|
||||
this.editForm.privateDeploy = this.pickRecognizedValue(getValueByName('私有化部署', 'private_deploy'), this.editForm.privateDeploy)
|
||||
},
|
||||
syncExampleFromCurl(curlCode) {
|
||||
const bodyMatch = curlCode.match(/(?:-d|--data|--data-raw)\s+['"]([\s\S]*?)['"](?:\s|$)/)
|
||||
if (!bodyMatch || !bodyMatch[1]) return
|
||||
|
||||
this.editForm.examples = [{
|
||||
tag: this.editForm.modelField || 'chat_completion_v1',
|
||||
content: bodyMatch[1]
|
||||
}]
|
||||
},
|
||||
pickRecognizedValue(value, fallback) {
|
||||
if (value === undefined || value === null || value === '') return fallback
|
||||
return String(value).trim()
|
||||
},
|
||||
toDisplayText(value) {
|
||||
if (value === undefined || value === null) return ''
|
||||
if (typeof value === 'object') return JSON.stringify(value)
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.model-edit-dialog {
|
||||
/deep/ .el-dialog {
|
||||
border-radius: 12px;
|
||||
margin-top: 40px !important;
|
||||
}
|
||||
|
||||
/deep/ .el-dialog__header {
|
||||
padding: 18px 22px 12px;
|
||||
border-bottom: 1px solid #edf1f7;
|
||||
}
|
||||
|
||||
/deep/ .el-dialog__title {
|
||||
color: #1f2d3d;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/deep/ .el-dialog__body {
|
||||
max-height: 68vh;
|
||||
padding: 18px 22px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/deep/ .el-dialog__footer {
|
||||
padding: 12px 22px 18px;
|
||||
border-top: 1px solid #edf1f7;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-form {
|
||||
.form-section {
|
||||
padding-bottom: 18px;
|
||||
margin-bottom: 18px;
|
||||
border-bottom: 1px dashed #e5e7eb;
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin-bottom: 12px;
|
||||
color: #1f2d3d;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px 18px;
|
||||
}
|
||||
|
||||
.span-2 {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
/deep/ .el-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/deep/ .el-form-item__label {
|
||||
padding-bottom: 6px;
|
||||
color: #475467;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/deep/ .el-input,
|
||||
/deep/ .el-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/deep/ .el-radio {
|
||||
margin-right: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-recognition-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 82px;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo-upload {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 32px;
|
||||
|
||||
span {
|
||||
color: #98a2b3;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.section-between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.dynamic-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dynamic-row {
|
||||
display: grid;
|
||||
grid-template-columns: 24px minmax(0, 180px) minmax(0, 1fr) 18px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
|
||||
.row-index {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
color: #1e6fff;
|
||||
font-size: 12px;
|
||||
background: #eef5ff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
i {
|
||||
color: #98a2b3;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.example-card-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.example-title-row {
|
||||
margin-top: -6px;
|
||||
}
|
||||
|
||||
.example-card {
|
||||
position: relative;
|
||||
padding: 12px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
|
||||
.example-field {
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
color: #667085;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .el-input__inner,
|
||||
/deep/ .el-textarea__inner {
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
}
|
||||
}
|
||||
|
||||
.example-remove {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
color: #98a2b3;
|
||||
cursor: pointer;
|
||||
background: #eef2f7;
|
||||
border-radius: 50%;
|
||||
|
||||
&:hover {
|
||||
color: #f56c6c;
|
||||
background: #fff1f2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.edit-form {
|
||||
.form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.span-2 {
|
||||
grid-column: span 1;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-recognition-row,
|
||||
.dynamic-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
309
f/web-kboss/src/views/operation/modelInfoConfig/index.vue
Normal file
309
f/web-kboss/src/views/operation/modelInfoConfig/index.vue
Normal file
@ -0,0 +1,309 @@
|
||||
<template>
|
||||
<div class="model-info-config-page">
|
||||
<h2 class="page-title">模型信息配置</h2>
|
||||
|
||||
<section class="filter-card">
|
||||
<div class="filter-row">
|
||||
<el-form :inline="true" :model="queryForm" class="filter-form">
|
||||
<el-form-item label="模型名称">
|
||||
<el-input
|
||||
v-model="queryForm.modelName"
|
||||
size="small"
|
||||
clearable
|
||||
placeholder="搜索模型名称"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型">
|
||||
<el-select v-model="queryForm.modelType" size="small" placeholder="全部">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option label="文本生成" value="文本生成"></el-option>
|
||||
<el-option label="图像生成" value="图像生成"></el-option>
|
||||
<el-option label="语音合成" value="语音合成"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="供应商">
|
||||
<el-select v-model="queryForm.provider" size="small" placeholder="全部">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option label="OpenAI" value="OpenAI"></el-option>
|
||||
<el-option label="Google" value="Google"></el-option>
|
||||
<el-option label="开元云" value="开元云"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button size="small" type="primary">查询</el-button>
|
||||
<el-button size="small">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="openAddDialog">添加</el-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="table-card">
|
||||
<el-tabs v-model="activeTab" class="config-tabs">
|
||||
<el-tab-pane label="待上架" name="pending"></el-tab-pane>
|
||||
<el-tab-pane label="已上架" name="listed"></el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<el-table :data="displayTableData" class="config-table" style="width: 100%">
|
||||
<el-table-column prop="id" label="模型ID" width="90" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="model_name" label="模型名称/版本" min-width="150" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="model_type" label="模型类型" width="120" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="provider" label="供应商" width="120" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="api_doc.api_url" label="接口地址" min-width="190" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="description" label="模型介绍" min-width="190" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-tag size="mini" :type="scope.row.listing_status === 1 ? 'success' : 'warning'">
|
||||
{{ scope.row.listing_status === 1 ? '已上架' : '待上架' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="updated_at" label="更新时间" width="150" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" @click="openDetailDialog(scope.row)">详情</el-button>
|
||||
<el-button type="text" size="mini" @click="openEditDialog(scope.row)">编辑模型信息</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="table-footer">
|
||||
<span>共 {{ displayTableData.length }} 条</span>
|
||||
<div class="pager">
|
||||
<el-button size="mini">上一页</el-button>
|
||||
<span>1 / 1</span>
|
||||
<el-button size="mini">下一页</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<model-info-edit-dialog ref="editDialog" @success="handleDialogSuccess"></model-info-edit-dialog>
|
||||
<model-info-detail-dialog ref="detailDialog"></model-info-detail-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ModelInfoEditDialog from './ModelInfoEditDialog.vue'
|
||||
import ModelInfoDetailDialog from './ModelInfoDetailDialog.vue'
|
||||
import { reqModelInfoConfigList } from '@/api/model/model'
|
||||
export default {
|
||||
name: 'ModelInfoConfig',
|
||||
components: {
|
||||
ModelInfoEditDialog,
|
||||
ModelInfoDetailDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'pending',
|
||||
queryForm: {
|
||||
modelName: '',
|
||||
modelType: '',
|
||||
provider: ''
|
||||
},
|
||||
tableData: [
|
||||
{
|
||||
id: 'M006',
|
||||
name: 'GPT-3.5-Turbo',
|
||||
type: '文本生成',
|
||||
provider: 'OpenAI',
|
||||
logo: '-',
|
||||
apiUrl: 'https://api.openai.com/v1/chat/completions',
|
||||
description: 'GPT-3.5-Turbo高效对话模型,适合文本生成和问答。',
|
||||
status: '待上架',
|
||||
updatedAt: '2026-04-15'
|
||||
},
|
||||
{
|
||||
id: 'M007',
|
||||
name: 'ViT-B/16',
|
||||
type: '图像生成',
|
||||
provider: 'Google',
|
||||
logo: '-',
|
||||
apiUrl: 'https://api.kaiyuan.cloud/v1/images',
|
||||
description: 'ViT-B/16视觉Transformer模型,适合图像理解。',
|
||||
status: '待上架',
|
||||
updatedAt: '2026-04-14'
|
||||
},
|
||||
{
|
||||
id: 'M008',
|
||||
name: 'Bark',
|
||||
type: '语音合成',
|
||||
provider: '开元云',
|
||||
logo: '-',
|
||||
apiUrl: 'https://api.kaiyuan.cloud/v1/audio',
|
||||
description: 'Bark多语言语音合成模型,支持自然语音生成。',
|
||||
status: '待上架',
|
||||
updatedAt: '2026-04-13'
|
||||
},
|
||||
{
|
||||
id: 'M009',
|
||||
name: 'CLIP',
|
||||
type: '多模态',
|
||||
provider: 'OpenAI',
|
||||
logo: '-',
|
||||
apiUrl: 'https://api.kaiyuan.cloud/v1/clip',
|
||||
description: 'CLIP多模态模型,支持图文检索和理解。',
|
||||
status: '待上架',
|
||||
updatedAt: '2026-04-12'
|
||||
},
|
||||
{
|
||||
id: 'M011',
|
||||
name: 'Falcon-180B',
|
||||
type: '文本生成',
|
||||
provider: 'Meta',
|
||||
logo: '-',
|
||||
apiUrl: 'https://api.kaiyuan.cloud/v1/chat',
|
||||
description: 'Falcon-180B开源大语言模型,适合复杂文本任务。',
|
||||
status: '待上架',
|
||||
updatedAt: '2026-04-27'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
displayTableData() {
|
||||
const targetStatus = this.activeTab === 'listed' ? 1 : 0
|
||||
return this.tableData.filter(item => Number(item.listing_status || 0) === targetStatus)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getModelInfoConfigList()
|
||||
},
|
||||
methods: {
|
||||
// 获取模型信息配置列表
|
||||
async getModelInfoConfigList() {
|
||||
const res = await reqModelInfoConfigList()
|
||||
console.log(res);
|
||||
if (res.status === true) {
|
||||
this.tableData = res.data.model_list
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
openAddDialog() {
|
||||
this.$refs.editDialog.open()
|
||||
},
|
||||
openDetailDialog(row) {
|
||||
this.$refs.detailDialog.open(row)
|
||||
},
|
||||
openEditDialog(row) {
|
||||
this.$refs.editDialog.open(row)
|
||||
},
|
||||
handleDialogSuccess() {
|
||||
this.getModelInfoConfigList()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.model-info-config-page {
|
||||
min-height: 100vh;
|
||||
padding: 16px 18px 24px;
|
||||
background: #f3f7ff;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin: 0 0 16px;
|
||||
color: #1f2d3d;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.filter-card,
|
||||
.table-card {
|
||||
background: #ffffff;
|
||||
border: 1px solid #edf1f7;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 24px rgba(31, 45, 61, 0.04);
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
padding: 12px 14px 0;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.filter-form {
|
||||
flex: 1;
|
||||
|
||||
/deep/ .el-form-item {
|
||||
margin-right: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/deep/ .el-input,
|
||||
/deep/ .el-select {
|
||||
width: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.filter-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
padding: 0 12px 12px;
|
||||
}
|
||||
|
||||
.config-tabs {
|
||||
/deep/ .el-tabs__header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/deep/ .el-tabs__nav-wrap::after {
|
||||
height: 1px;
|
||||
background: #e8edf5;
|
||||
}
|
||||
|
||||
/deep/ .el-tabs__item {
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
.config-table {
|
||||
margin-top: 10px;
|
||||
color: #344054;
|
||||
|
||||
/deep/ th {
|
||||
color: #475467;
|
||||
font-weight: 600;
|
||||
background: #fafcff;
|
||||
}
|
||||
|
||||
/deep/ td,
|
||||
/deep/ th {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
/deep/ .cell {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.table-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 0 0;
|
||||
color: #667085;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.pager {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user