243 lines
9.2 KiB
Vue
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>
|