feat: AI 助手模型管理(CRUD、会话绑定、自动化验收)
- Flyway V6:模型表、默认种子、ai_chat_session.model_id 回填 - 管理端 /api/admin/ai/models;用户端选模与新会话绑定 - 禁用模型后会话只读(5009);chat 按会话 modelCode 调 DashScope - 前端 AiModels 页、助手模型选择器与 localStorage - 单测、AiModelManagementApiIT、Playwright E2E;e2e Profile 关闭注解限流 - 技术实施文档与验收报告;移除过期冲突分析文档 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="page-title">AI 模型管理</h2>
|
||||
<div class="filter-bar">
|
||||
<el-button type="primary" @click="showAddDialog">新增模型</el-button>
|
||||
</div>
|
||||
<el-card class="table-card">
|
||||
<el-table :data="models" v-loading="loading">
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="displayName" label="展示名称" min-width="140" />
|
||||
<el-table-column prop="modelCode" label="模型编码" min-width="140" />
|
||||
<el-table-column prop="description" label="描述" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.enabled ? 'success' : 'danger'">{{ row.enabled ? '启用' : '禁用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="图片" width="70">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.allowImageUpload ? '是' : '否' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="语音" width="70">
|
||||
<template #default="{ row }">
|
||||
<span>{{ row.allowVoiceUpload ? '是' : '否' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="默认" width="70">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.isDefault" type="warning" size="small">默认</el-tag>
|
||||
<span v-else>—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sort" label="排序" width="70" />
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button text type="primary" @click="showEditDialog(row)">编辑</el-button>
|
||||
<el-button text type="danger" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="isEdit ? '编辑模型' : '新增模型'" width="520px">
|
||||
<el-form :model="form" label-width="120px">
|
||||
<el-form-item label="展示名称" required>
|
||||
<el-input v-model="form.displayName" maxlength="32" placeholder="如:通义 Turbo · 快速" />
|
||||
</el-form-item>
|
||||
<el-form-item label="模型编码" required>
|
||||
<el-input v-model="form.modelCode" maxlength="64" placeholder="如:qwen-turbo" />
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="form.description" type="textarea" :rows="2" maxlength="200" placeholder="可选,如:更强推理,适合复杂问题" />
|
||||
</el-form-item>
|
||||
<el-form-item label="启用">
|
||||
<el-switch v-model="form.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="允许上传图片">
|
||||
<el-switch v-model="form.allowImageUpload" />
|
||||
</el-form-item>
|
||||
<el-form-item label="允许上传语音">
|
||||
<el-switch v-model="form.allowVoiceUpload" />
|
||||
</el-form-item>
|
||||
<el-form-item label="默认模型">
|
||||
<el-switch v-model="form.isDefault" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序">
|
||||
<el-input-number v-model="form.sort" :min="0" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { listAdminAiModels, createAdminAiModel, updateAdminAiModel, deleteAdminAiModel } from '@/api/aiModel'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const models = ref([])
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const editingId = ref(null)
|
||||
const form = ref({
|
||||
modelCode: '',
|
||||
displayName: '',
|
||||
description: '',
|
||||
enabled: true,
|
||||
allowImageUpload: false,
|
||||
allowVoiceUpload: false,
|
||||
isDefault: false,
|
||||
sort: 0,
|
||||
})
|
||||
|
||||
async function loadModels() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await listAdminAiModels()
|
||||
models.value = res.data || []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function showAddDialog() {
|
||||
isEdit.value = false
|
||||
editingId.value = null
|
||||
form.value = { modelCode: '', displayName: '', description: '', enabled: true, allowImageUpload: false, allowVoiceUpload: false, isDefault: false, sort: 0 }
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
function showEditDialog(row) {
|
||||
isEdit.value = true
|
||||
editingId.value = row.id
|
||||
form.value = {
|
||||
modelCode: row.modelCode,
|
||||
displayName: row.displayName,
|
||||
description: row.description || '',
|
||||
enabled: row.enabled,
|
||||
allowImageUpload: row.allowImageUpload,
|
||||
allowVoiceUpload: row.allowVoiceUpload,
|
||||
isDefault: row.isDefault,
|
||||
sort: row.sort,
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!form.value.displayName.trim()) {
|
||||
ElMessage.warning('请输入展示名称')
|
||||
return
|
||||
}
|
||||
if (!form.value.modelCode.trim()) {
|
||||
ElMessage.warning('请输入模型编码')
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (isEdit.value) {
|
||||
await updateAdminAiModel(editingId.value, form.value)
|
||||
} else {
|
||||
await createAdminAiModel(form.value)
|
||||
}
|
||||
ElMessage.success('操作成功')
|
||||
dialogVisible.value = false
|
||||
loadModels()
|
||||
} catch {
|
||||
// error handled by interceptor
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(row) {
|
||||
await ElMessageBox.confirm(`确定删除模型"${row.displayName}"?`, '提示')
|
||||
try {
|
||||
await deleteAdminAiModel(row.id)
|
||||
ElMessage.success('已删除')
|
||||
loadModels()
|
||||
} catch {
|
||||
// error handled by interceptor
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadModels)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-title {
|
||||
color: var(--text-primary);
|
||||
font-size: var(--font-size-xl);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
.filter-bar {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
align-items: center;
|
||||
}
|
||||
.table-card {
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
.table-card :deep(.el-card__body) { padding: 0; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user