修复智能体bug
This commit is contained in:
@@ -22,6 +22,12 @@
|
||||
{{ detail.id }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="请求协议" label-width="130px">
|
||||
<el-tag size="small" type="primary" effect="plain">
|
||||
{{ protocolLabel(detail.protocol) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="接口地址" label-width="130px">
|
||||
<div class="url-text">{{ detail.base_url || '—' }}</div>
|
||||
<el-tag
|
||||
@@ -43,6 +49,10 @@
|
||||
<div class="url-text">{{ detail.custom_url || '—' }}</div>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="实际请求" label-width="130px">
|
||||
<div class="endpoint-preview">{{ endpointPreview || '—' }}</div>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="API Key" label-width="130px">
|
||||
<div v-if="apiKeys.length > 0" class="key-list">
|
||||
<div v-for="(k, idx) in apiKeys" :key="idx" class="key-item">
|
||||
@@ -120,6 +130,9 @@ import {
|
||||
providerInitial,
|
||||
normalizeModels,
|
||||
normalizeApiKeys,
|
||||
protocolLabel,
|
||||
previewEndpoint,
|
||||
effectiveUrl,
|
||||
} from '../constants'
|
||||
|
||||
const visible = ref(false)
|
||||
@@ -129,6 +142,12 @@ const detail = ref(null)
|
||||
const models = computed(() => normalizeModels(detail.value?.models))
|
||||
const apiKeys = computed(() => normalizeApiKeys(detail.value?.api_keys))
|
||||
|
||||
// 实际请求地址,规则与后端一致
|
||||
const endpointPreview = computed(() => {
|
||||
if (!detail.value) return ''
|
||||
return previewEndpoint(detail.value.protocol, effectiveUrl(detail.value))
|
||||
})
|
||||
|
||||
async function open(id) {
|
||||
detail.value = null
|
||||
visible.value = true
|
||||
@@ -219,6 +238,18 @@ defineExpose({ open })
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.endpoint-preview {
|
||||
padding: 6px 10px;
|
||||
background: #f5f7fa;
|
||||
border: 1px dashed var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
font-size: 12px;
|
||||
color: #409eff;
|
||||
word-break: break-all;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.mt6 {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
@@ -35,10 +35,22 @@
|
||||
<span class="form-tip">用于区分不同上游来源,内容完全由你决定</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="请求协议" prop="protocol">
|
||||
<el-select v-model="form.protocol" placeholder="请选择请求协议" style="width: 100%">
|
||||
<el-option
|
||||
v-for="p in PROTOCOLS"
|
||||
:key="p.value"
|
||||
:label="p.label"
|
||||
:value="p.value"
|
||||
/>
|
||||
</el-select>
|
||||
<span class="form-tip">{{ currentProtocolDesc }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="接口地址" prop="base_url">
|
||||
<el-input
|
||||
v-model="form.base_url"
|
||||
placeholder="例如:https://api.openai.com/v1"
|
||||
:placeholder="urlPlaceholder"
|
||||
:disabled="form.use_custom_url"
|
||||
clearable
|
||||
/>
|
||||
@@ -56,6 +68,11 @@
|
||||
<span class="form-tip">勾选自定义后,实际调用将使用此地址,上方地址仅作留档</span>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 实时预览最终请求地址,避免拼错到测试时才发现 -->
|
||||
<el-form-item v-if="endpointPreview" label="实际请求">
|
||||
<div class="endpoint-preview">{{ endpointPreview }}</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="API Key" prop="api_keys">
|
||||
<div class="keys-box">
|
||||
<div v-if="form.api_keys.length === 0" class="no-key">暂未添加密钥</div>
|
||||
@@ -168,7 +185,14 @@ import { ref, reactive, computed, nextTick } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Plus, Delete } from '@element-plus/icons-vue'
|
||||
import { getAgentApiDetail, createAgentApi, updateAgentApi } from '@/api/agentapimanagement'
|
||||
import { normalizeModels, normalizeApiKeys } from '../constants'
|
||||
import {
|
||||
normalizeModels,
|
||||
normalizeApiKeys,
|
||||
PROTOCOLS,
|
||||
PROTOCOL_MAP,
|
||||
DEFAULT_PROTOCOL,
|
||||
previewEndpoint,
|
||||
} from '../constants'
|
||||
|
||||
const emit = defineEmits(['saved'])
|
||||
|
||||
@@ -193,6 +217,7 @@ const inputRef = ref(null)
|
||||
const form = reactive({
|
||||
id: 0,
|
||||
provider: '',
|
||||
protocol: DEFAULT_PROTOCOL,
|
||||
base_url: '',
|
||||
use_custom_url: false,
|
||||
custom_url: '',
|
||||
@@ -202,8 +227,34 @@ const form = reactive({
|
||||
remark: '',
|
||||
})
|
||||
|
||||
// 当前协议的说明文案
|
||||
const currentProtocolDesc = computed(
|
||||
() => PROTOCOL_MAP[form.protocol]?.desc || ''
|
||||
)
|
||||
|
||||
// 地址输入框的占位提示随协议变化
|
||||
const urlPlaceholder = computed(() => {
|
||||
if (form.protocol === 'custom') {
|
||||
return '填写完整端点,例如:https://your-gateway.com/invoke'
|
||||
}
|
||||
if (form.protocol === 'anthropic') {
|
||||
return '填根地址,例如:https://api.anthropic.com/v1'
|
||||
}
|
||||
if (form.protocol === 'gemini') {
|
||||
return '填根地址,例如:https://generativelanguage.googleapis.com/v1beta'
|
||||
}
|
||||
return '填根地址,例如:https://api.openai.com/v1'
|
||||
})
|
||||
|
||||
// 最终请求地址预览,规则与后端一致
|
||||
const endpointPreview = computed(() => {
|
||||
const url = form.use_custom_url ? form.custom_url : form.base_url
|
||||
return previewEndpoint(form.protocol, url)
|
||||
})
|
||||
|
||||
const rules = {
|
||||
provider: [{ required: true, message: '请输入上游接口名称', trigger: 'blur' }],
|
||||
protocol: [{ required: true, message: '请选择请求协议', trigger: 'change' }],
|
||||
base_url: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
@@ -329,6 +380,7 @@ function validateKeys() {
|
||||
function resetForm() {
|
||||
form.id = 0
|
||||
form.provider = ''
|
||||
form.protocol = DEFAULT_PROTOCOL
|
||||
form.base_url = ''
|
||||
form.use_custom_url = false
|
||||
form.custom_url = ''
|
||||
@@ -394,6 +446,7 @@ async function loadInto(id) {
|
||||
const d = res.data
|
||||
form.id = d.id
|
||||
form.provider = d.provider || ''
|
||||
form.protocol = d.protocol || DEFAULT_PROTOCOL
|
||||
form.base_url = d.base_url || ''
|
||||
form.use_custom_url = Number(d.use_custom_url) === 1
|
||||
form.custom_url = d.custom_url || ''
|
||||
@@ -430,6 +483,7 @@ async function submit() {
|
||||
|
||||
const payload = {
|
||||
provider: form.provider.trim(),
|
||||
protocol: form.protocol,
|
||||
base_url: form.base_url || '',
|
||||
use_custom_url: form.use_custom_url ? 1 : 0,
|
||||
custom_url: form.use_custom_url ? form.custom_url : '',
|
||||
@@ -479,6 +533,19 @@ defineExpose({ open, openCopy })
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.endpoint-preview {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
background: var(--el-fill-color-lighter);
|
||||
border: 1px dashed var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
font-size: 12px;
|
||||
color: #409eff;
|
||||
word-break: break-all;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.keys-box {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
<el-form-item label="上游接口">
|
||||
<el-input v-model="info.provider_label" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="请求协议">
|
||||
<el-input v-model="info.protocol_label" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="生效地址">
|
||||
<el-input v-model="info.url" disabled />
|
||||
</el-form-item>
|
||||
@@ -88,6 +91,9 @@
|
||||
<div class="result-msg" :class="{ error: !r.success }">
|
||||
{{ r.message || (r.success ? '连接正常' : '连接异常') }}
|
||||
</div>
|
||||
<div v-if="r.endpoint" class="result-endpoint">
|
||||
实际请求:{{ r.endpoint }}
|
||||
</div>
|
||||
<pre v-if="r.response" class="result-response">{{ r.response }}</pre>
|
||||
<pre v-if="!r.success && r.detail" class="result-response error">{{ r.detail }}</pre>
|
||||
</div>
|
||||
@@ -111,6 +117,7 @@ import {
|
||||
normalizeModels,
|
||||
normalizeApiKeys,
|
||||
maskKey,
|
||||
protocolLabel,
|
||||
} from '../constants'
|
||||
|
||||
const visible = ref(false)
|
||||
@@ -128,6 +135,7 @@ const results = ref([])
|
||||
|
||||
const info = reactive({
|
||||
provider_label: '',
|
||||
protocol_label: '',
|
||||
url: '',
|
||||
})
|
||||
|
||||
@@ -152,6 +160,7 @@ async function open(id) {
|
||||
if (res?.code === 200 && res.data) {
|
||||
const d = res.data
|
||||
info.provider_label = providerLabel(d.provider)
|
||||
info.protocol_label = protocolLabel(d.protocol)
|
||||
info.url = effectiveUrl(d)
|
||||
models.value = normalizeModels(d.models)
|
||||
selectedModel.value = models.value[0] || ''
|
||||
@@ -308,6 +317,15 @@ defineExpose({ open })
|
||||
}
|
||||
}
|
||||
|
||||
.result-endpoint {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
color: #909399;
|
||||
word-break: break-all;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.result-response {
|
||||
margin: 8px 0 0;
|
||||
padding: 8px 10px;
|
||||
|
||||
@@ -2,9 +2,75 @@
|
||||
* 智能体 API 管理 - 共享工具
|
||||
*
|
||||
* 上游接口(provider)为用户自由填写的文本,不做枚举限制。
|
||||
* 这里只提供展示辅助(配色、首字母徽标)与数据规整能力。
|
||||
* 请求协议(protocol)显式选择,决定实际请求哪个端点路径。
|
||||
*/
|
||||
|
||||
/**
|
||||
* 请求协议枚举,与后端 models.AgentProtocol* 常量保持一致
|
||||
* path 为在接口地址之后追加的路径,custom 与 gemini 特殊处理
|
||||
*/
|
||||
export const PROTOCOLS = [
|
||||
{
|
||||
value: 'chat_completions',
|
||||
label: 'Chat Completions API',
|
||||
path: '/chat/completions',
|
||||
desc: 'OpenAI 标准对话接口,绝大多数上游与中转都兼容',
|
||||
},
|
||||
{
|
||||
value: 'responses',
|
||||
label: 'Responses API',
|
||||
path: '/responses',
|
||||
desc: 'OpenAI 新版接口,请求体用 input 替代 messages',
|
||||
},
|
||||
{
|
||||
value: 'anthropic',
|
||||
label: 'Anthropic Messages API',
|
||||
path: '/messages',
|
||||
desc: 'Claude 官方协议,鉴权头为 x-api-key',
|
||||
},
|
||||
{
|
||||
value: 'gemini',
|
||||
label: 'Google Gemini',
|
||||
path: '/models/{model}:generateContent',
|
||||
desc: '模型名嵌在路径中,密钥通过 query 传递',
|
||||
},
|
||||
{
|
||||
value: 'custom',
|
||||
label: '自定义(直接请求填写的地址)',
|
||||
path: '',
|
||||
desc: '不追加任何路径,把填写的地址当完整端点请求',
|
||||
},
|
||||
];
|
||||
|
||||
export const PROTOCOL_MAP = Object.fromEntries(PROTOCOLS.map((p) => [p.value, p]));
|
||||
|
||||
export const DEFAULT_PROTOCOL = 'chat_completions';
|
||||
|
||||
/** 协议展示名 */
|
||||
export function protocolLabel(value) {
|
||||
return PROTOCOL_MAP[value]?.label || value || '未设置协议';
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览实际请求地址,与后端 buildAgentEndpoint 的规则一致
|
||||
* 供表单实时提示用,避免地址拼错才在测试时发现
|
||||
*/
|
||||
export function previewEndpoint(protocol, url, model = '{model}') {
|
||||
const base = String(url || '').trim().replace(/\/+$/, '');
|
||||
if (!base) return '';
|
||||
|
||||
if (protocol === 'custom') return base;
|
||||
|
||||
if (protocol === 'gemini') {
|
||||
return `${base}/models/${model}:generateContent?key=***`;
|
||||
}
|
||||
|
||||
const path = PROTOCOL_MAP[protocol]?.path || '/chat/completions';
|
||||
// 地址已以该路径结尾时不重复追加
|
||||
if (base.toLowerCase().endsWith(path.toLowerCase())) return base;
|
||||
return base + path;
|
||||
}
|
||||
|
||||
/** 徽标配色池,按上游名称哈希稳定取色,保证同一上游每次渲染颜色一致 */
|
||||
const BADGE_COLORS = [
|
||||
'#10a37f',
|
||||
|
||||
@@ -108,6 +108,9 @@
|
||||
<div class="card-section">
|
||||
<div class="section-label">
|
||||
接口地址
|
||||
<el-tag size="small" type="primary" effect="plain">
|
||||
{{ protocolLabel(row.protocol) }}
|
||||
</el-tag>
|
||||
<el-tag v-if="Number(row.use_custom_url) === 1" type="warning" size="small" effect="plain">
|
||||
自定义
|
||||
</el-tag>
|
||||
@@ -264,6 +267,7 @@ import {
|
||||
normalizeModels,
|
||||
normalizeApiKeys,
|
||||
maskKey,
|
||||
protocolLabel,
|
||||
} from './constants'
|
||||
import AgentApiEdit from './components/edit.vue'
|
||||
import AgentApiDetail from './components/detail.vue'
|
||||
|
||||
Reference in New Issue
Block a user