@@ -23,13 +23,6 @@
< / el-select >
< / el-form-item >
< / el-col >
< el-col :xs = "24" :sm = "12" :md = "12" :lg = "12" :xl = "12" class = "mb20" >
< el-form-item label = "运营商名称" prop = "operatorName" required >
< el-select v-model = "state.ruleForm.operatorName" placeholder="请选择运营商" clearable style="width: 100%" >
< el -option v-for = "item in operatorNameOptions" :key="item.value" :label="item.label" :value="item.value" > < / el -option >
< / el-select >
< / el-form-item >
< / el-col >
< el-col :xs = "24" :sm = "12" :md = "12" :lg = "12" :xl = "12" class = "mb20" >
< el-form-item label = "模型服务地址" prop = "baseUrl" >
< el-input v-model = "state.ruleForm.baseUrl" placeholder="请输入模型服务地址" clearable > < / el-input >
@@ -45,6 +38,9 @@
< / el-select >
< / el-form-item >
< / el-col >
< el-col v-if = "props.isSuperAdmin || state.ruleForm.isPrivate === 1" :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20 provider-section-col" >
< div class = "provider-section-title" > 服务商模型配置 < / div >
< / el-col >
< el-col v-if = "!props.isSuperAdmin" :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" >
< el -form -item label = "访问类型" prop = "isPrivate" >
< el-radio-group v-model = "state.ruleForm.isPrivate" @change="onIsPrivateChange" >
@@ -53,6 +49,13 @@
< / el-radio-group >
< / el-form-item >
< / el-col >
< el-col v-if = "props.isSuperAdmin || state.ruleForm.isPrivate === 1" :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" >
< el -form -item label = "运营商名称" prop = "operatorName" required >
< el-select v-model = "state.ruleForm.operatorName" placeholder="请选择运营商" clearable style="width: 100%" >
< el -option v-for = "item in operatorNameOptions" :key="item.value" :label="item.label" :value="item.value" > < / el -option >
< / el-select >
< / el-form-item >
< / el-col >
< el-col v-if = "!props.isSuperAdmin && state.ruleForm.isPrivate === 1" :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20" >
< el -form -item label = "API 密钥" prop = "apiKey" required >
< el-input v-model = "state.ruleForm.apiKey" type="password" show-password placeholder="请输入 API 密钥字符串" clearable > < / el-input >
@@ -103,11 +106,6 @@
< / el-radio-group >
< / el-form-item >
< / el-col >
< el-col :xs = "24" :sm = "24" :md = "24" :lg = "24" :xl = "24" class = "mb20" >
< el-form-item label = "备注说明" prop = "remark" >
< el-input v-model = "state.ruleForm.remark" type="textarea" placeholder="请输入备注说明" :rows="3" clearable > < / el-input >
< / el-form-item >
< / el-col >
< / el-row >
<!-- 高级配置折叠区域 -- >
@@ -405,12 +403,21 @@
< / template >
< / el-dialog >
<!-- 请求映射配置弹窗 -- >
< el-dialog v-model = "showRequestMappingDialog" title="配置请求映射" width="60 0px" :close-on-click-modal="false" >
< el-dialog v-model = "showRequestMappingDialog" title="配置请求映射" width="8 60px" :close-on-click-modal="false" >
< div class = "mapping-config-container" >
< div v-for = "(field, index) in state.requestMappingFields" :key="index" class="mapping-field-item" >
< el -input v-model = "field.key" placeholder="请输入字段名 (Key)" style="width: 40%" clearable > < / el-input>
< el -input v-model = "field.key" placeholder="请输入字段名 (Key)" style="width: 18%" clearable @input="syncRequestSpecialFieldsOnKeyChange(index)" > < / el -input >
< span class = "separator" > = < / span >
< el-input v-model = "field.value" placeholder="请输入字段值 (Value)" style="width: 40 %" clearable > < / el-input >
< el-input v-model = "field.value" placeholder="请输入字段值 (Value)" style="width: 18 %" clearable > < / el-input >
< el-button : type = "field.required ? 'success' : 'primary'" :plain = "!field.required" @click ="toggleRequiredField(index)" size = "small" >
{ { field . required ? '✓ 必填字段' : '设置必填字段' } }
< / el-button >
< el-button : type = "field.isFirstFrame ? 'success' : 'primary'" :plain = "!field.isFirstFrame" @click ="setFirstFrameField(index)" size = "small" >
{ { field . isFirstFrame ? '✓ 首帧参数' : '设置首帧参数' } }
< / el-button >
< el-button : type = "field.isLastFrame ? 'success' : 'primary'" :plain = "!field.isLastFrame" @click ="setLastFrameField(index)" size = "small" >
{ { field . isLastFrame ? '✓ 尾帧参数' : '设置尾帧参数' } }
< / el-button >
< el-button type = "danger" link @click ="removeRequestMappingField(index)" > 删除 < / el -button >
< / div >
< el-button type = "primary" link @click ="addRequestMappingField" style = "margin-top: 10px" > + 添加字段 < / el-button >
@@ -436,7 +443,7 @@
> < / el-input >
< span class = "separator" > = < / span >
< el-input v-model = "field.value" placeholder="请输入字段值 (Value)" style="width: 30%" clearable > < / el-input >
< el-button : type = "field.isTokenField ? 'warning ' : 'primary'" :plain = "!field.isTokenField" @click ="setTokenField(index)" size = "small" >
< el-button : type = "field.isTokenField ? 'success ' : 'primary'" :plain = "!field.isTokenField" @click ="setTokenField(index)" size = "small" >
{ { field . isTokenField ? '✓ 计费字段' : '设置计费字段' } }
< / el-button >
< el-button : type = "field.isMainBody ? 'success' : 'primary'" :plain = "!field.isMainBody" @click ="setMainBody(index)" size = "small" >
@@ -531,6 +538,22 @@ export interface FormField {
options ? : FormFieldOption [ ] ; // 选项列表
}
export interface KeyValueField {
key : string ;
value : string ;
}
export interface RequestMappingField extends KeyValueField {
required ? : boolean ;
isFirstFrame ? : boolean ;
isLastFrame ? : boolean ;
}
export interface ResponseMappingField extends KeyValueField {
isMainBody ? : boolean ;
isTokenField ? : boolean ;
}
const props = withDefaults (
defineProps < {
modelTypes ? : ModelTypeOption [ ] ;
@@ -597,6 +620,9 @@ const state = reactive({
enabled : 1 ,
isChatModel : 0 ,
callMode : 0 ,
firstFrame : '' ,
lastFrame : '' ,
requiredFields : [ ] as string [ ] ,
maxConcurrency : 10 ,
queueLimit : 100 ,
timeoutSeconds : 30 ,
@@ -604,7 +630,6 @@ const state = reactive({
retryTimes : 3 ,
retryQueueMaxSeconds : 60 ,
autoCleanSeconds : 300 ,
remark : '' ,
extendMapping : '{}' ,
responseTokenField : '' ,
tokenConfig : '{}' ,
@@ -678,7 +703,22 @@ const state = reactive({
trigger : 'change' ,
} ,
] ,
operatorName : [ { required : true , message : '请选择运营商名称' , trigger : 'change' } ] ,
operatorName : [
{
validator : ( _rule : unknown , value : unknown , callback : ( e ? : Error ) => void ) => {
if ( ! ( props . isSuperAdmin || state . ruleForm . isPrivate === 1 ) ) {
callback ( ) ;
return ;
}
if ( ! value || String ( value ) . trim ( ) === '' ) {
callback ( new Error ( '请选择运营商名称' ) ) ;
return ;
}
callback ( ) ;
} ,
trigger : 'change' ,
} ,
] ,
apiKey : [
{
validator : ( _rule : unknown , value : unknown , callback : ( e ? : Error ) => void ) => {
@@ -713,6 +753,11 @@ const state = reactive({
callback ( new Error ( '请求映射字段名不能为空' ) ) ;
return ;
}
const duplicatedSpecialSelection = state . requestMappingFields . some ( ( x ) => x . isFirstFrame && x . isLastFrame ) ;
if ( duplicatedSpecialSelection ) {
callback ( new Error ( '同一行不能同时设置为首帧和尾帧参数' ) ) ;
return ;
}
callback ( ) ;
} ,
trigger : 'change' ,
@@ -777,12 +822,12 @@ const state = reactive({
detailLoading : false ,
} ,
showAdvanced : false ,
headers : [ ] as Array < { key : string ; value : string } > ,
headers : [ ] as KeyValueField [ ] ,
formFields : [ ] as Array < FormField > ,
requestMappingFields : [ ] as Array < { key : string ; value : string } > ,
responseMappingFields : [ ] as Array < { key : string ; value : string ; isMainBody ? : boolean ; isTokenField ? : boolean } > ,
extendMappingFields : [ ] as Array < { key : string ; value : string } > ,
tokenConfigFields : [ ] as Array < { key : string ; value : string } > ,
requestMappingFields : [ ] as RequestMappingField [ ] ,
responseMappingFields : [ ] as ResponseMappingField [ ] ,
extendMappingFields : [ ] as KeyValueField [ ] ,
tokenConfigFields : [ ] as KeyValueField [ ] ,
mainBodyIndex : - 1 , // 记录哪一行被设置为返回主体
} ) ;
@@ -806,7 +851,7 @@ const createEmptyFormField = (): FormField => {
} ;
// 将数组转换为对象
const fieldsToObject = ( fields : Array < { key : string ; value : string } > ) => {
const fieldsToObject = ( fields : KeyValueField [ ] ) => {
const obj : Record < string , string > = { } ;
fields . forEach ( ( f ) => {
if ( f . key && f . key . trim ( ) ) {
@@ -820,7 +865,7 @@ const fieldsToObject = (fields: Array<{ key: string; value: string }>) => {
// 1. 旧格式:逗号分隔的"key1:value1,key2:value2"字符串
// 2. JSON字符串: 格式化为JSON对象的字符串
// 3. 新格式: 直接是Record<string, string>对象
const parseHeaders = ( raw : unknown ) : Array < { key : string ; value : string } > => {
const parseHeaders = ( raw : unknown ) : KeyValueField [ ] => {
if ( ! raw ) return [ ] ;
// 如果是字符串, 先尝试解析为JSON, 如果失败则尝试解析为旧格式key:value,key2:value2
@@ -1051,11 +1096,78 @@ const confirmFormFields = () => {
} ;
// 请求映射字段操作
const addRequestMappingField = ( ) => {
state . requestMappingFields . push ( { key : '' , value : '' } ) ;
state . requestMappingFields . push ( { key : '' , value : '' , required : false , isFirstFrame : false , isLastFrame : false } ) ;
} ;
const removeRequestMappingField = ( index : number ) => {
const removed = state . requestMappingFields [ index ] ;
state . requestMappingFields . splice ( index , 1 ) ;
if ( removed ? . required ) {
state . ruleForm . requiredFields = state . ruleForm . requiredFields . filter ( ( item ) => item !== String ( removed . key || '' ) . trim ( ) ) ;
}
if ( removed ? . isFirstFrame ) {
state . ruleForm . firstFrame = '' ;
}
if ( removed ? . isLastFrame ) {
state . ruleForm . lastFrame = '' ;
}
} ;
const toggleRequiredField = ( index : number ) => {
const row = state . requestMappingFields [ index ] ;
if ( ! row ) return ;
row . required = ! row . required ;
state . ruleForm . requiredFields = state . requestMappingFields
. filter ( ( field ) => field . required )
. map ( ( field ) => String ( field . key || '' ) . trim ( ) )
. filter ( Boolean ) ;
} ;
const setFirstFrameField = ( index : number ) => {
state . requestMappingFields . forEach ( ( field , i ) => {
field . isFirstFrame = i === index ;
if ( i === index ) {
field . isLastFrame = false ;
}
} ) ;
state . ruleForm . firstFrame = state . requestMappingFields [ index ] ? . key ? . trim ? . ( ) || '' ;
if ( state . requestMappingFields [ index ] ? . isLastFrame ) {
state . ruleForm . lastFrame = '' ;
}
} ;
const setLastFrameField = ( index : number ) => {
state . requestMappingFields . forEach ( ( field , i ) => {
field . isLastFrame = i === index ;
if ( i === index ) {
field . isFirstFrame = false ;
}
} ) ;
state . ruleForm . lastFrame = state . requestMappingFields [ index ] ? . key ? . trim ? . ( ) || '' ;
if ( state . requestMappingFields [ index ] ? . isFirstFrame ) {
state . ruleForm . firstFrame = '' ;
}
} ;
const syncRequestSpecialFieldsOnKeyChange = ( index : number ) => {
const row = state . requestMappingFields [ index ] ;
if ( ! row ) return ;
if ( row . required ) {
const nextKey = row . key ? . trim ? . ( ) || '' ;
state . ruleForm . requiredFields = state . requestMappingFields
. filter ( ( field ) => field . required )
. map ( ( field ) => String ( field . key || '' ) . trim ( ) )
. filter ( Boolean ) ;
if ( ! nextKey ) {
row . required = false ;
}
}
if ( row . isFirstFrame ) {
state . ruleForm . firstFrame = row . key ? . trim ? . ( ) || '' ;
}
if ( row . isLastFrame ) {
state . ruleForm . lastFrame = row . key ? . trim ? . ( ) || '' ;
}
} ;
const confirmRequestMappingFields = ( ) => {
@@ -1101,7 +1213,7 @@ const confirmResponseMappingFields = () => {
showResponseMappingDialog . value = false ;
} ;
const ensureKeyValueRows = ( rows : Array < { key : string ; value : string } > ) => ( rows . length ? rows : [ { key : '' , value : '' } ] ) ;
const ensureKeyValueRows = < T extends KeyValueField > (rows : T [ ] , createEmpty : ( ) => T ) : T [ ] => ( rows . length ? rows : [ createEmpty ( ) ] ) ;
// 解析旧格式的form数据兼容到新格式
const parseFormFieldsUnified = ( raw : unknown ) : Array < FormField > => {
@@ -1182,6 +1294,9 @@ const fillFormFromDetailRow = (row: Record<string, unknown>) => {
enabled : Number ( row . enabled ? ? 1 ) ,
isChatModel : row . isChatModel !== undefined && row . isChatModel !== null ? Number ( row . isChatModel ) : 0 ,
callMode : row . callMode !== undefined && row . callMode !== null ? Number ( row . callMode ) : row . isAsync !== undefined && row . isAsync !== null ? Number ( row . isAsync ) : 0 ,
firstFrame : String ( row . firstFrame || '' ) ,
lastFrame : String ( row . lastFrame || '' ) ,
requiredFields : Array . isArray ( row . requiredFields ) ? row . requiredFields . map ( ( item ) => String ( item || '' ) . trim ( ) ) . filter ( Boolean ) : [ ] ,
maxConcurrency : Number ( row . maxConcurrency ? ? 10 ) ,
queueLimit : Number ( row . queueLimit ? ? 100 ) ,
timeoutSeconds ,
@@ -1189,17 +1304,33 @@ const fillFormFromDetailRow = (row: Record<string, unknown>) => {
retryTimes : Number ( row . retryTimes ? ? 3 ) ,
retryQueueMaxSeconds : Number ( row . retryQueueMaxSeconds ? ? 60 ) ,
autoCleanSeconds : Number ( row . autoCleanSeconds ? ? 300 ) ,
remark : String ( row . remark || '' ) ,
extendMapping : '{}' ,
responseTokenField : String ( row . responseTokenField || '' ) ,
tokenConfig : '{}' ,
} ;
state . headers = ensureKeyValueRows ( parseHeaders ( row . headMsg ) ) ;
state . headers = ensureKeyValueRows ( parseHeaders ( row . headMsg ) , ( ) => ( { key : '' , value : '' } ) );
state . formFields = parseFormFieldsUnified ( row . form ) ;
state . requestMappingFields = ensureKeyValueRows ( parseRequestMappingFields ( row . requestMapping ) ) ;
state . responseMappingFields = ensureKeyValueRows ( parseResponseMappingFields ( row . responseMapping ) ) ;
state . extendMappingFields = ensureKeyValueRows ( parseFieldsUnified ( row . extendMapping ) ) ;
state . tokenConfigFields = ensureKeyValueRows ( parseFieldsUnified ( row . tokenConfig ) ) ;
state . requestMappingFields = ensureKeyValueRows ( parseRequestMappingFields ( row . requestMapping ) as RequestMappingField [ ] , ( ) => ( {
key : '' ,
value : '' ,
required : false ,
isFirstFrame : false ,
isLastFrame : false ,
} ) ) ;
state . requestMappingFields . forEach ( ( field ) => {
const fieldKey = String ( field . key || '' ) . trim ( ) ;
field . required = fieldKey !== '' && state . ruleForm . requiredFields . includes ( fieldKey ) ;
field . isFirstFrame = fieldKey !== '' && fieldKey === String ( row . firstFrame || '' ) . trim ( ) ;
field . isLastFrame = fieldKey !== '' && fieldKey === String ( row . lastFrame || '' ) . trim ( ) ;
} ) ;
state . responseMappingFields = ensureKeyValueRows ( parseResponseMappingFields ( row . responseMapping ) as ResponseMappingField [ ] , ( ) => ( {
key : '' ,
value : '' ,
isMainBody : false ,
isTokenField : false ,
} ) ) ;
state . extendMappingFields = ensureKeyValueRows ( parseFieldsUnified ( row . extendMapping ) , ( ) => ( { key : '' , value : '' } ) ) ;
state . tokenConfigFields = ensureKeyValueRows ( parseFieldsUnified ( row . tokenConfig ) , ( ) => ( { key : '' , value : '' } ) ) ;
// 根据 responseTokenField 字段设置计费字段标记(单选)
const tokenFieldKey = String ( row . responseTokenField || '' ) . trim ( ) ;
@@ -1230,7 +1361,7 @@ const fillFormFromDetailRow = (row: Record<string, unknown>) => {
asyncQueryConfigForm . resultPath = String ( qc . result _path || '' ) ;
asyncQueryConfigForm . statusPath = String ( qc . status _path || '' ) ;
asyncQueryConfigForm . intervalSeconds = Number ( qc . interval _seconds ? ? 2 ) || 2 ;
asyncQueryConfigForm . statusValueFields = ensureKeyValueRows ( objectToFields ( ( qc . status _values as Record < string , unknown > ) || { } ) ) ;
asyncQueryConfigForm . statusValueFields = ensureKeyValueRows ( objectToFields ( ( qc . status _values as Record < string , unknown > ) || { } ) , ( ) => ( { key : '' , value : '' } ) );
} else {
asyncQueryConfigForm . url = '' ;
asyncQueryConfigForm . method = 'POST' ;
@@ -1292,6 +1423,9 @@ const openDialog = async (type: string, row?: Record<string, unknown>) => {
enabled : 1 ,
isChatModel : 0 ,
callMode : 0 ,
firstFrame : '' ,
lastFrame : '' ,
requiredFields : [ ] ,
maxConcurrency : 10 ,
queueLimit : 100 ,
timeoutSeconds : 30 ,
@@ -1299,14 +1433,13 @@ const openDialog = async (type: string, row?: Record<string, unknown>) => {
retryTimes : 3 ,
retryQueueMaxSeconds : 60 ,
autoCleanSeconds : 300 ,
remark : '' ,
extendMapping : '{}' ,
responseTokenField : '' ,
tokenConfig : '{}' ,
} ;
state . headers = [ { key : '' , value : '' } ] ;
state . formFields = [ createEmptyFormField ( ) ] ;
state . requestMappingFields = [ { key : '' , value : '' } ] ;
state . requestMappingFields = [ { key : '' , value : '' , required : false , isFirstFrame : false , isLastFrame : false } ] ;
state . responseMappingFields = [ { key : '' , value : '' , isMainBody : false , isTokenField : false } ] ;
state . mainBodyIndex = - 1 ;
state . extendMappingFields = [ { key : '' , value : '' } ] ;
@@ -1363,6 +1496,10 @@ const onSubmit = () => {
// 过滤掉空键名的字段
const requestMapping = fieldsToObject ( state . requestMappingFields . filter ( ( f ) => String ( f . key || '' ) . trim ( ) !== '' ) ) ;
const requiredFields = state . requestMappingFields
. filter ( ( f ) => Boolean ( f . required ) && String ( f . key || '' ) . trim ( ) !== '' )
. map ( ( f ) => String ( f . key || '' ) . trim ( ) ) ;
state . ruleForm . requiredFields = requiredFields ;
const responseMapping = fieldsToObject ( state . responseMappingFields . filter ( ( f ) => String ( f . key || '' ) . trim ( ) !== '' ) ) ;
// 获取被设置为返回主体的字段 {key: value}
@@ -1419,6 +1556,9 @@ const onSubmit = () => {
apiKey : state . ruleForm . isPrivate === 1 ? String ( state . ruleForm . apiKey ? ? '' ) . trim ( ) : '' ,
form : processedFormFields ,
requestMapping ,
requiredFields ,
firstFrame : String ( state . ruleForm . firstFrame || '' ) . trim ( ) ,
lastFrame : String ( state . ruleForm . lastFrame || '' ) . trim ( ) ,
responseMapping ,
responseBody ,
maxConcurrency : state . ruleForm . maxConcurrency ,
@@ -1428,7 +1568,6 @@ const onSubmit = () => {
retryTimes : state . ruleForm . retryTimes ,
retryQueueMaxSeconds : state . ruleForm . retryQueueMaxSeconds ,
autoCleanSeconds : state . ruleForm . autoCleanSeconds ,
remark : state . ruleForm . remark || '' ,
extendMapping : fieldsToUnknownObject ( state . extendMappingFields . filter ( ( f ) => String ( f . key || '' ) . trim ( ) !== '' ) ) ,
responseTokenField ,
tokenConfig : fieldsToUnknownObject ( state . tokenConfigFields . filter ( ( f ) => String ( f . key || '' ) . trim ( ) !== '' ) ) ,
@@ -1482,6 +1621,17 @@ onMounted(() => {
width : 100 % ;
}
. provider - section - col {
margin - bottom : 0 ;
}
. provider - section - title {
font - size : 13 px ;
font - weight : 600 ;
color : # 606266 ;
padding : 2 px 0 4 px ;
}
. form - config - container {
max - height : 550 px ;
overflow - y : auto ;