|
|
@@ -0,0 +1,528 @@
|
|
|
+<template>
|
|
|
+ <div class="app-container">
|
|
|
+ <el-card>
|
|
|
+ <div slot="header" class="card-header">
|
|
|
+ <span>AI模型配置管理</span>
|
|
|
+ <div>
|
|
|
+ <el-button type="primary" size="small" @click="handleAddModel" v-if="activeTab === 'models'">
|
|
|
+ <i class="el-icon-plus"></i>新增模型
|
|
|
+ </el-button>
|
|
|
+ <el-button type="success" size="small" @click="handleRefresh">
|
|
|
+ <i class="el-icon-refresh"></i>刷新缓存
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-alert
|
|
|
+ title="统一模型配置说明"
|
|
|
+ description="所有文本AI模型统一在此管理,按sort_order排序优先级(越小越优先)。每个场景可独立配置使用哪些模型及流水线顺序。FastGPT、TTS语音、豆包视觉及图像/语音相关模型不在此处管理。"
|
|
|
+ type="info" show-icon :closable="false" style="margin-bottom: 20px"
|
|
|
+ />
|
|
|
+
|
|
|
+ <el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
|
|
+ <!-- ===== 模型列表 Tab ===== -->
|
|
|
+ <el-tab-pane label="模型列表" name="models">
|
|
|
+ <el-table :data="modelList" v-loading="modelLoading" border size="small" row-key="id"
|
|
|
+ style="width:100%">
|
|
|
+ <el-table-column label="排序" width="70" align="center">
|
|
|
+ <template slot-scope="{ row, $index }">
|
|
|
+ <span class="sort-handle" style="cursor: move; font-size: 18px;">☰</span>
|
|
|
+ <span style="margin-left: 4px;">{{ $index + 1 }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="modelName" label="模型名称" min-width="120" />
|
|
|
+ <el-table-column prop="providerCode" label="供应商" width="90" />
|
|
|
+ <el-table-column prop="modelIdentifier" label="模型标识" min-width="160" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="apiEndpoint" label="API地址" min-width="200" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="maxTokens" label="MaxToken" width="90" align="center" />
|
|
|
+ <el-table-column prop="temperature" label="温度" width="70" align="center" />
|
|
|
+ <el-table-column prop="sortOrder" label="排序号" width="70" align="center" />
|
|
|
+ <el-table-column prop="status" label="状态" width="70" align="center">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-tag :type="row.status === 1 ? 'success' : 'danger'" size="small">
|
|
|
+ {{ row.status === 1 ? '启用' : '禁用' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="280" fixed="right">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-button type="primary" size="mini" @click="handleEditModel(row)">编辑</el-button>
|
|
|
+ <el-button type="success" size="mini" @click="handleTestModel(row)">测试</el-button>
|
|
|
+ <el-popconfirm title="确定删除该模型吗?" @confirm="handleDeleteModel(row)">
|
|
|
+ <el-button slot="reference" type="danger" size="mini">删除</el-button>
|
|
|
+ </el-popconfirm>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <div style="margin-top: 12px; text-align: right;">
|
|
|
+ <el-button type="warning" size="small" @click="handleSaveSort" :disabled="!sortChanged">
|
|
|
+ <i class="el-icon-sort"></i>保存排序
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
+ <!-- ===== 场景配置 Tab ===== -->
|
|
|
+ <el-tab-pane label="场景配置" name="scenes">
|
|
|
+ <el-row :gutter="16">
|
|
|
+ <!-- 左侧:场景列表 -->
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-card shadow="never" class="scene-list-card">
|
|
|
+ <div slot="header" class="sub-header">使用场景</div>
|
|
|
+ <div v-loading="sceneLoading">
|
|
|
+ <div v-for="scene in sceneList" :key="scene.sceneCode"
|
|
|
+ class="scene-item"
|
|
|
+ :class="{ active: selectedScene === scene.sceneCode }"
|
|
|
+ @click="selectScene(scene)">
|
|
|
+ <div class="scene-name">
|
|
|
+ {{ scene.sceneName }}
|
|
|
+ <el-tag size="mini" :type="scene.sceneType === 'multi_pipeline' ? 'warning' : 'info'" style="margin-left: 6px;">
|
|
|
+ {{ scene.sceneType === 'multi_pipeline' ? '多模型' : '单模型' }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ <div class="scene-code">{{ scene.sceneCode }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <!-- 右侧:场景详情 -->
|
|
|
+ <el-col :span="16">
|
|
|
+ <el-card shadow="never" v-if="selectedSceneInfo">
|
|
|
+ <div slot="header" class="sub-header">
|
|
|
+ {{ selectedSceneInfo.sceneName }} - 模型配置
|
|
|
+ <el-tag size="small" :type="selectedSceneInfo.status === 1 ? 'success' : 'danger'" style="margin-left: 8px;">
|
|
|
+ {{ selectedSceneInfo.status === 1 ? '启用' : '禁用' }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 场景基础信息 -->
|
|
|
+ <el-form :model="sceneForm" label-width="100px" size="small" inline>
|
|
|
+ <el-form-item label="场景编码">{{ selectedSceneInfo.sceneCode }}</el-form-item>
|
|
|
+ <el-form-item label="场景类型">
|
|
|
+ {{ selectedSceneInfo.sceneType === 'multi_pipeline' ? '多模型流水线' : '单模型' }}
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="流水线类型" v-if="selectedSceneInfo.sceneType === 'multi_pipeline'">
|
|
|
+ {{ selectedSceneInfo.pipelineType === 'scoring' ? '质量评分链' : selectedSceneInfo.pipelineType === 'sequential' ? '顺序调用' : '-' }}
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="质量阈值" v-if="selectedSceneInfo.pipelineType === 'scoring'">
|
|
|
+ <el-input-number v-model="sceneForm.qualityThreshold" :min="60" :max="160" :step="10" size="mini" style="width: 100px;" />
|
|
|
+ <el-button type="primary" size="mini" @click="saveThreshold" style="margin-left: 8px;">保存</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <el-divider />
|
|
|
+
|
|
|
+ <!-- 场景关联模型列表 -->
|
|
|
+ <div style="margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center;">
|
|
|
+ <span>关联模型流水线(按 pipeline_order 排序)</span>
|
|
|
+ <el-button type="primary" size="mini" @click="handleBindModel">
|
|
|
+ <i class="el-icon-plus"></i>绑定模型
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-table :data="sceneModelList" v-loading="sceneModelLoading" border size="small"
|
|
|
+ row-key="id" style="width:100%">
|
|
|
+ <el-table-column label="顺序" width="60" align="center">
|
|
|
+ <template slot-scope="{ $index }">
|
|
|
+ <span class="sort-handle" style="cursor: move;">☰</span>
|
|
|
+ <span style="margin-left: 4px;">{{ $index + 1 }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="模型名称" min-width="120">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ {{ row.model ? row.model.modelName : '模型#' + row.modelId }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="供应商" width="80">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ {{ row.model ? row.model.providerCode : '-' }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="流水线角色" width="110" align="center">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-tag size="mini" :type="row.role === 'scorer' ? 'warning' : 'success'">
|
|
|
+ {{ row.role === 'scorer' ? '评分者' : '生成者' }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="pipelineOrder" label="顺序号" width="70" align="center" />
|
|
|
+ <el-table-column prop="sortWeight" label="权重" width="60" align="center" />
|
|
|
+ <el-table-column label="操作" width="120" fixed="right">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-button type="text" size="mini" @click="handleEditSceneModel(row)">编辑</el-button>
|
|
|
+ <el-popconfirm title="确定移除此关联?" @confirm="handleRemoveSceneModel(row)">
|
|
|
+ <el-button slot="reference" type="text" size="mini" style="color: #F56C6C;">移除</el-button>
|
|
|
+ </el-popconfirm>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ <div v-if="sceneModelList.length === 0" style="text-align: center; color: #999; padding: 40px 0;">
|
|
|
+ 暂未绑定模型,请点击"绑定模型"添加
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <el-empty v-else description="请从左侧选择一个场景" />
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 模型编辑弹窗 -->
|
|
|
+ <el-dialog :title="modelDialogTitle" :visible.sync="modelDialogVisible" width="560px" @closed="resetModelForm">
|
|
|
+ <el-form :model="modelForm" :rules="modelRules" ref="modelFormRef" label-width="120px">
|
|
|
+ <el-form-item label="模型名称" prop="modelName">
|
|
|
+ <el-input v-model="modelForm.modelName" placeholder="如:豆包Pro 32K" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="供应商" prop="providerCode">
|
|
|
+ <el-select v-model="modelForm.providerCode" placeholder="请选择" style="width: 100%">
|
|
|
+ <el-option label="豆包(Doubao)" value="doubao" />
|
|
|
+ <el-option label="通义千问(Qwen)" value="qwen" />
|
|
|
+ <el-option label="元宝(Yuanbao)" value="yuanbao" />
|
|
|
+ <el-option label="DeepSeek" value="deepseek" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="模型标识" prop="modelIdentifier">
|
|
|
+ <el-input v-model="modelForm.modelIdentifier" placeholder="如:doubao-1-5-pro-32k" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="API地址" prop="apiEndpoint">
|
|
|
+ <el-input v-model="modelForm.apiEndpoint" placeholder="API Endpoint" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="API Key" prop="apiKey">
|
|
|
+ <el-input v-model="modelForm.apiKey" placeholder="API Key" show-password />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="最大Token">
|
|
|
+ <el-input-number v-model="modelForm.maxTokens" :min="512" :max="131072" :step="512" style="width: 180px;" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="温度参数">
|
|
|
+ <el-slider v-model="modelForm.temperature" :min="0" :max="2" :step="0.1" show-input style="width: 200px;" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="排序号">
|
|
|
+ <el-input-number v-model="modelForm.sortOrder" :min="0" :max="9999" style="width: 180px;" />
|
|
|
+ <span style="color: #909399; font-size: 12px; margin-left: 8px;">越小越优先</span>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="状态">
|
|
|
+ <el-switch v-model="modelForm.status" :active-value="1" :inactive-value="0" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <div slot="footer">
|
|
|
+ <el-button @click="modelDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="handleSubmitModel">确定</el-button>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 场景绑定模型弹窗 -->
|
|
|
+ <el-dialog title="绑定模型到场景" :visible.sync="bindDialogVisible" width="480px">
|
|
|
+ <el-form label-width="100px" size="small">
|
|
|
+ <el-form-item label="选择模型">
|
|
|
+ <el-select v-model="bindForm.modelId" placeholder="请选择模型" style="width: 100%" filterable>
|
|
|
+ <el-option v-for="m in modelList" :key="m.id"
|
|
|
+ :label="m.modelName + ' (' + m.providerCode + ')'" :value="m.id"
|
|
|
+ :disabled="m.status !== 1" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="流水线角色">
|
|
|
+ <el-radio-group v-model="bindForm.role">
|
|
|
+ <el-radio label="generator">生成者</el-radio>
|
|
|
+ <el-radio label="scorer">评分者</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="流水线顺序">
|
|
|
+ <el-input-number v-model="bindForm.pipelineOrder" :min="0" :max="99" size="small" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="权重">
|
|
|
+ <el-input-number v-model="bindForm.sortWeight" :min="0" :max="99" size="small" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <div slot="footer">
|
|
|
+ <el-button @click="bindDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="handleConfirmBind">确定</el-button>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 场景模型编辑弹窗 -->
|
|
|
+ <el-dialog title="编辑场景模型" :visible.sync="editSceneModelDialog" width="420px">
|
|
|
+ <el-form label-width="100px" size="small">
|
|
|
+ <el-form-item label="流水线角色">
|
|
|
+ <el-radio-group v-model="editSceneModelForm.role">
|
|
|
+ <el-radio label="generator">生成者</el-radio>
|
|
|
+ <el-radio label="scorer">评分者</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="流水线顺序">
|
|
|
+ <el-input-number v-model="editSceneModelForm.pipelineOrder" :min="0" :max="99" size="small" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="权重">
|
|
|
+ <el-input-number v-model="editSceneModelForm.sortWeight" :min="0" :max="99" size="small" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <div slot="footer">
|
|
|
+ <el-button @click="editSceneModelDialog = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="handleUpdateSceneModel">确定</el-button>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import {
|
|
|
+ listAiModel, getAiModel, addAiModel, updateAiModel, deleteAiModel,
|
|
|
+ batchSortAiModel, testAiModel, refreshAiModel,
|
|
|
+ listAiScene, updateAiScene, updateSceneThreshold,
|
|
|
+ getSceneModels, addSceneModel, deleteSceneModel, updateSceneModel, refreshAiScene
|
|
|
+} from '@/api/admin/aiModel'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'AiModelConfig',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ activeTab: 'models',
|
|
|
+ // 模型
|
|
|
+ modelLoading: false,
|
|
|
+ modelList: [],
|
|
|
+ modelDialogVisible: false,
|
|
|
+ modelDialogTitle: '',
|
|
|
+ modelForm: {
|
|
|
+ id: null, modelName: '', providerCode: '', modelIdentifier: '',
|
|
|
+ apiEndpoint: '', apiKey: '', maxTokens: 4096, temperature: 0.7,
|
|
|
+ sortOrder: 0, status: 1
|
|
|
+ },
|
|
|
+ modelRules: {
|
|
|
+ modelName: [{ required: true, message: '请输入模型名称', trigger: 'blur' }],
|
|
|
+ providerCode: [{ required: true, message: '请选择供应商', trigger: 'change' }],
|
|
|
+ modelIdentifier: [{ required: true, message: '请输入模型标识', trigger: 'blur' }],
|
|
|
+ apiEndpoint: [{ required: true, message: '请输入API地址', trigger: 'blur' }],
|
|
|
+ apiKey: [{ required: true, message: '请输入API Key', trigger: 'blur' }]
|
|
|
+ },
|
|
|
+ sortChanged: false,
|
|
|
+ // 场景
|
|
|
+ sceneLoading: false,
|
|
|
+ sceneList: [],
|
|
|
+ selectedScene: '',
|
|
|
+ selectedSceneInfo: null,
|
|
|
+ sceneForm: { qualityThreshold: 120 },
|
|
|
+ sceneModelLoading: false,
|
|
|
+ sceneModelList: [],
|
|
|
+ bindDialogVisible: false,
|
|
|
+ bindForm: { modelId: null, role: 'generator', pipelineOrder: 0, sortWeight: 0 },
|
|
|
+ editSceneModelDialog: false,
|
|
|
+ editSceneModelForm: { id: null, role: 'generator', pipelineOrder: 0, sortWeight: 0 }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ this.loadModels()
|
|
|
+ this.loadScenes()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // ─── 模型 CRUD ───
|
|
|
+ async loadModels() {
|
|
|
+ this.modelLoading = true
|
|
|
+ try {
|
|
|
+ const res = await listAiModel()
|
|
|
+ this.modelList = res.data || []
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('获取模型列表失败')
|
|
|
+ } finally {
|
|
|
+ this.modelLoading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ handleAddModel() {
|
|
|
+ this.modelDialogTitle = '新增模型'
|
|
|
+ this.modelForm = {
|
|
|
+ id: null, modelName: '', providerCode: '', modelIdentifier: '',
|
|
|
+ apiEndpoint: '', apiKey: '', maxTokens: 4096, temperature: 0.7,
|
|
|
+ sortOrder: 0, status: 1
|
|
|
+ }
|
|
|
+ this.modelDialogVisible = true
|
|
|
+ this.$nextTick(() => { this.$refs.modelFormRef && this.$refs.modelFormRef.resetFields() })
|
|
|
+ },
|
|
|
+ handleEditModel(row) {
|
|
|
+ this.modelDialogTitle = '编辑模型'
|
|
|
+ this.modelForm = { ...row }
|
|
|
+ this.modelDialogVisible = true
|
|
|
+ },
|
|
|
+ async handleSubmitModel() {
|
|
|
+ try {
|
|
|
+ await this.$refs.modelFormRef.validate()
|
|
|
+ if (this.modelForm.id) {
|
|
|
+ await updateAiModel(this.modelForm.id, this.modelForm)
|
|
|
+ this.$message.success('修改成功')
|
|
|
+ } else {
|
|
|
+ await addAiModel(this.modelForm)
|
|
|
+ this.$message.success('新增成功')
|
|
|
+ }
|
|
|
+ this.modelDialogVisible = false
|
|
|
+ await this.loadModels()
|
|
|
+ } catch (e) {
|
|
|
+ if (e !== false) this.$message.error('操作失败')
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async handleDeleteModel(row) {
|
|
|
+ try {
|
|
|
+ await deleteAiModel(row.id)
|
|
|
+ this.$message.success('删除成功')
|
|
|
+ await this.loadModels()
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('删除失败')
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async handleTestModel(row) {
|
|
|
+ try {
|
|
|
+ const res = await testAiModel(row.id)
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ const result = res.data
|
|
|
+ if (result.success) {
|
|
|
+ this.$message.success('连接成功!响应:' + (result.response || 'OK'))
|
|
|
+ } else {
|
|
|
+ this.$message.error('连接失败:' + (result.error || '未知错误'))
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.$message.error('测试失败')
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('测试请求失败')
|
|
|
+ }
|
|
|
+ },
|
|
|
+ handleSaveSort() {
|
|
|
+ const sortList = this.modelList.map((m, i) => ({ id: m.id, sortOrder: i }))
|
|
|
+ batchSortAiModel(sortList).then(() => {
|
|
|
+ this.$message.success('排序已保存')
|
|
|
+ this.sortChanged = false
|
|
|
+ this.loadModels()
|
|
|
+ }).catch(() => {
|
|
|
+ this.$message.error('保存排序失败')
|
|
|
+ })
|
|
|
+ },
|
|
|
+ resetModelForm() {
|
|
|
+ this.modelForm = {
|
|
|
+ id: null, modelName: '', providerCode: '', modelIdentifier: '',
|
|
|
+ apiEndpoint: '', apiKey: '', maxTokens: 4096, temperature: 0.7,
|
|
|
+ sortOrder: 0, status: 1
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // ─── 场景 ───
|
|
|
+ async loadScenes() {
|
|
|
+ this.sceneLoading = true
|
|
|
+ try {
|
|
|
+ const res = await listAiScene()
|
|
|
+ this.sceneList = res.data || []
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('获取场景列表失败')
|
|
|
+ } finally {
|
|
|
+ this.sceneLoading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ selectScene(scene) {
|
|
|
+ this.selectedScene = scene.sceneCode
|
|
|
+ this.selectedSceneInfo = scene
|
|
|
+ this.sceneForm.qualityThreshold = scene.qualityThreshold || 120
|
|
|
+ this.loadSceneModels(scene.sceneCode)
|
|
|
+ },
|
|
|
+ async loadSceneModels(sceneCode) {
|
|
|
+ this.sceneModelLoading = true
|
|
|
+ try {
|
|
|
+ const res = await getSceneModels(sceneCode)
|
|
|
+ this.sceneModelList = res.data || []
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('获取场景模型列表失败')
|
|
|
+ } finally {
|
|
|
+ this.sceneModelLoading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async saveThreshold() {
|
|
|
+ try {
|
|
|
+ await updateSceneThreshold(this.selectedScene, this.sceneForm.qualityThreshold)
|
|
|
+ this.$message.success('阈值已更新')
|
|
|
+ this.selectedSceneInfo.qualityThreshold = this.sceneForm.qualityThreshold
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('更新失败')
|
|
|
+ }
|
|
|
+ },
|
|
|
+ handleBindModel() {
|
|
|
+ this.bindForm = { modelId: null, role: 'generator', pipelineOrder: this.sceneModelList.length, sortWeight: 0 }
|
|
|
+ this.bindDialogVisible = true
|
|
|
+ },
|
|
|
+ async handleConfirmBind() {
|
|
|
+ if (!this.bindForm.modelId) {
|
|
|
+ this.$message.warning('请选择模型')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ await addSceneModel(this.selectedScene, this.bindForm)
|
|
|
+ this.$message.success('绑定成功')
|
|
|
+ this.bindDialogVisible = false
|
|
|
+ this.loadSceneModels(this.selectedScene)
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('绑定失败')
|
|
|
+ }
|
|
|
+ },
|
|
|
+ handleEditSceneModel(row) {
|
|
|
+ this.editSceneModelForm = {
|
|
|
+ id: row.id,
|
|
|
+ role: row.role || 'generator',
|
|
|
+ pipelineOrder: row.pipelineOrder || 0,
|
|
|
+ sortWeight: row.sortWeight || 0
|
|
|
+ }
|
|
|
+ this.editSceneModelDialog = true
|
|
|
+ },
|
|
|
+ async handleUpdateSceneModel() {
|
|
|
+ try {
|
|
|
+ await updateSceneModel(this.selectedScene, this.editSceneModelForm.id, this.editSceneModelForm)
|
|
|
+ this.$message.success('更新成功')
|
|
|
+ this.editSceneModelDialog = false
|
|
|
+ this.loadSceneModels(this.selectedScene)
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('更新失败')
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async handleRemoveSceneModel(row) {
|
|
|
+ try {
|
|
|
+ await deleteSceneModel(this.selectedScene, row.id)
|
|
|
+ this.$message.success('已移除')
|
|
|
+ this.loadSceneModels(this.selectedScene)
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('移除失败')
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // ─── 通用 ───
|
|
|
+ handleTabClick() {
|
|
|
+ if (this.activeTab === 'models') this.loadModels()
|
|
|
+ if (this.activeTab === 'scenes') this.loadScenes()
|
|
|
+ },
|
|
|
+ async handleRefresh() {
|
|
|
+ try {
|
|
|
+ await refreshAiModel()
|
|
|
+ await refreshAiScene()
|
|
|
+ this.$message.success('缓存已刷新')
|
|
|
+ if (this.activeTab === 'models') this.loadModels()
|
|
|
+ if (this.activeTab === 'scenes') this.loadScenes()
|
|
|
+ } catch (e) {
|
|
|
+ this.$message.error('刷新失败')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.card-header { display: flex; justify-content: space-between; align-items: center; }
|
|
|
+.sub-header { font-weight: 600; font-size: 14px; }
|
|
|
+.scene-list-card { max-height: 600px; overflow-y: auto; }
|
|
|
+.scene-item {
|
|
|
+ padding: 10px 12px;
|
|
|
+ border: 1px solid #EBEEF5;
|
|
|
+ border-radius: 4px;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+.scene-item:hover { border-color: #409EFF; background: #ECF5FF; }
|
|
|
+.scene-item.active { border-color: #409EFF; background: #ECF5FF; }
|
|
|
+.scene-name { font-weight: 500; font-size: 13px; }
|
|
|
+.scene-code { color: #909399; font-size: 11px; margin-top: 2px; }
|
|
|
+.sort-handle { cursor: move; color: #909399; display: inline-block; vertical-align: middle; }
|
|
|
+.sort-handle:hover { color: #409EFF; }
|
|
|
+</style>
|