Files
video-factory/ui-src/src/components/UserConfigDialog.vue
T
2026-08-05 08:53:43 +08:00

243 lines
9.2 KiB
Vue

<template>
<el-dialog v-model="visible" title="我的模型配置" width="820px" top="10vh" destroy-on-close @closed="handleClose">
<el-form label-width="80px" label-position="top" size="small">
<el-row :gutter="20">
<el-col :span="12">
<h4 class="section-title">对话模型</h4>
<el-form-item label="模型">
<el-select v-model="selectedChatId" placeholder="请选择对话模型" clearable filterable style="width:100%">
<el-option v-for="m in chatModels" :key="m.modelConfigId" :label="m.modelName" :value="m.modelConfigId" />
</el-select>
<div v-if="selectedChatModel" class="form-tip">模型并发数: {{ selectedChatModel.concurrencyCount }}</div>
</el-form-item>
<template v-if="selectedChatId">
<el-form-item label="API Key">
<el-input v-model="chatForm.apiKey" type="password" show-password placeholder="sk-..." />
</el-form-item>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="端点地址">
<el-input v-model="chatForm.endpointUrl" placeholder="https://api.openai.com" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="接口地址">
<el-input v-model="chatForm.requestPath" placeholder="/v1/chat/completions" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="Temperature">
<el-slider v-model="chatForm.temperature" :min="0" :max="2" :step="0.05" style="width:100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="Max Tokens">
<el-input-number v-model="chatForm.maxTokens" :min="256" :max="selectedChatMaxTokens" :step="512" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="并发数">
<el-input-number v-model="chatForm.concurrencyCount" :min="1" :max="100" style="width:100%" />
<div class="el-form-item__tip">并发数必须大于0</div>
</el-form-item>
</el-col>
<el-col :span="12" />
</el-row>
<div class="form-tip">Temperature: {{ chatForm.temperature.toFixed(2) }} | Max Tokens上限 {{ selectedChatMaxTokens || 'N/A' }} | 并发数: {{ chatForm.concurrencyCount }}</div>
</template>
</el-col>
<el-col :span="12">
<h4 class="section-title">视频生成模型</h4>
<el-form-item label="模型">
<el-select v-model="selectedVideoId" placeholder="请选择视频生成模型" clearable filterable style="width:100%">
<el-option v-for="m in videoModels" :key="m.modelConfigId" :label="m.modelName" :value="m.modelConfigId" />
</el-select>
<div v-if="selectedVideoModel" class="form-tip">模型并发数: {{ selectedVideoModel.concurrencyCount }}</div>
</el-form-item>
<template v-if="selectedVideoId">
<el-form-item label="API Key">
<el-input v-model="videoForm.apiKey" type="password" show-password placeholder="sk-..." />
</el-form-item>
<el-row :gutter="10">
<el-col :span="12">
<el-form-item label="端点地址">
<el-input v-model="videoForm.endpointUrl" placeholder="https://api.example.com" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="接口地址">
<el-input v-model="videoForm.requestPath" placeholder="/v1/video/generate" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="回调接口地址">
<el-input v-model="videoForm.callbackPath" placeholder="/v1/task/status" />
</el-form-item>
<el-form-item label="Temperature">
<el-slider v-model="videoForm.temperature" :min="0" :max="2" :step="0.05" style="width:100%" />
</el-form-item>
<el-form-item label="并发数">
<el-input-number v-model="videoForm.concurrencyCount" :min="1" :max="100" style="width:100%" />
<div class="el-form-item__tip">并发数必须大于0</div>
</el-form-item>
<div class="form-tip">当前值: {{ videoForm.temperature.toFixed(2) }} | 并发数: {{ videoForm.concurrencyCount }}</div>
</template>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, watch, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { getUserModelList, saveUserConfig } from '@/api/userConfig'
const props = defineProps({
modelValue: Boolean
})
const emit = defineEmits(['update:modelValue'])
const visible = ref(false)
const saving = ref(false)
const loading = ref(false)
const allModels = ref([])
const chatModels = computed(() => allModels.value.filter(m => m.modelType === 'chat'))
const videoModels = computed(() => allModels.value.filter(m => m.modelType === 'video'))
const selectedChatId = ref(null)
const selectedVideoId = ref(null)
const chatForm = reactive({ apiKey: '', endpointUrl: '', requestPath: '', temperature: 0.85, maxTokens: 4096, concurrencyCount: 0 })
const videoForm = reactive({ apiKey: '', endpointUrl: '', requestPath: '', callbackPath: '', temperature: 0.85, concurrencyCount: 0 })
const selectedChatModel = computed(() => {
return allModels.value.find(m => m.modelConfigId === selectedChatId.value) || null
})
const selectedVideoModel = computed(() => {
return allModels.value.find(m => m.modelConfigId === selectedVideoId.value) || null
})
const selectedChatMaxTokens = computed(() => {
return selectedChatModel.value?.systemMaxTokens || 4096
})
function findModelById(id) {
return allModels.value.find(m => m.modelConfigId === id) || null
}
watch(selectedChatId, (id) => {
const m = findModelById(id)
chatForm.apiKey = m?.userApiKey || ''
chatForm.endpointUrl = m?.endpointUrl || ''
chatForm.requestPath = m?.interfacePath || ''
chatForm.temperature = m?.temperature ?? 0.85
chatForm.maxTokens = m?.userMaxTokens || m?.systemMaxTokens || 4096
chatForm.concurrencyCount = m?.concurrencyCount || 1
})
watch(selectedVideoId, (id) => {
const m = findModelById(id)
videoForm.apiKey = m?.userApiKey || ''
videoForm.endpointUrl = m?.endpointUrl || ''
videoForm.requestPath = m?.interfacePath || ''
videoForm.callbackPath = m?.callbackPath || ''
videoForm.temperature = m?.temperature ?? 0.85
videoForm.concurrencyCount = m?.concurrencyCount || 1
})
watch(() => props.modelValue, async (val) => {
visible.value = val
if (val) {
await loadData()
}
})
function handleClose() {
emit('update:modelValue', false)
}
async function loadData() {
loading.value = true
try {
const res = await getUserModelList({ page: 1, pageSize: 999 })
allModels.value = res.list || []
const activeChat = allModels.value.find(m => m.modelType === 'chat' && m.configured)
selectedChatId.value = activeChat?.modelConfigId || null
const activeVideo = allModels.value.find(m => m.modelType === 'video' && m.configured)
selectedVideoId.value = activeVideo?.modelConfigId || null
} catch (e) {
} finally {
loading.value = false
}
}
async function handleSave() {
saving.value = true
try {
// 校验并发数必须大于0
if ((selectedChatId.value && (!chatForm.concurrencyCount || chatForm.concurrencyCount < 1)) ||
(selectedVideoId.value && (!videoForm.concurrencyCount || videoForm.concurrencyCount < 1))) {
ElMessage.warning('并发数必须大于0')
saving.value = false
return
}
const configs = []
if (selectedChatId.value) {
configs.push({
modelConfigId: selectedChatId.value,
apiKey: chatForm.apiKey,
endpointUrl: chatForm.endpointUrl,
requestPath: chatForm.requestPath,
temperature: chatForm.temperature,
maxTokens: chatForm.maxTokens,
concurrencyCount: chatForm.concurrencyCount
})
}
if (selectedVideoId.value) {
configs.push({
modelConfigId: selectedVideoId.value,
apiKey: videoForm.apiKey,
endpointUrl: videoForm.endpointUrl,
requestPath: videoForm.requestPath,
callbackPath: videoForm.callbackPath,
temperature: videoForm.temperature,
concurrencyCount: videoForm.concurrencyCount
})
}
await saveUserConfig({ configs })
ElMessage.success('保存成功')
visible.value = false
} catch (e) {
} finally {
saving.value = false
}
}
</script>
<style scoped>
.section-title {
margin: 0 0 4px 0;
font-size: 14px;
font-weight: 600;
color: #111;
}
.form-tip {
font-size: 12px;
color: #909399;
margin-top: 4px;
}
</style>