diff --git a/backend-single/src/main/java/com/emotion/service/ai/AiTemplateRenderer.java b/backend-single/src/main/java/com/emotion/service/ai/AiTemplateRenderer.java index eb6caff..61616eb 100644 --- a/backend-single/src/main/java/com/emotion/service/ai/AiTemplateRenderer.java +++ b/backend-single/src/main/java/com/emotion/service/ai/AiTemplateRenderer.java @@ -15,7 +15,15 @@ public class AiTemplateRenderer { Map inputs = new HashMap<>(); if (StringUtils.hasText(defaultInputs)) { try { - inputs.putAll(JSON.parseObject(defaultInputs)); + JSONObject parsed = JSON.parseObject(defaultInputs); + parsed.forEach((key, value) -> { + // 兼容 _meta 格式:{ "_meta": {...}, "value": "..." } + if (value instanceof JSONObject && ((JSONObject) value).containsKey("_meta")) { + inputs.put(key, ((JSONObject) value).get("value")); + } else { + inputs.put(key, value); + } + }); } catch (Exception ignored) { inputs.put("default_input", defaultInputs); } diff --git a/web-admin/src/types/aiconfig.ts b/web-admin/src/types/aiconfig.ts index 56c7ae0..7fc61ea 100644 --- a/web-admin/src/types/aiconfig.ts +++ b/web-admin/src/types/aiconfig.ts @@ -270,6 +270,23 @@ export interface AiEndpointRuntimeRequest { inputs: Record } +export interface TestParamField { + name: string + label: string + type: 'string' | 'textarea' | 'number' | 'boolean' + value: any + defaultValue: any + required: boolean +} + +export interface ParamDefinition { + name: string + label: string + type: 'string' | 'textarea' | 'number' | 'boolean' + defaultValue: any + required: boolean +} + export interface AiRuntimeTestResponse { sceneCode: string status: string diff --git a/web-admin/src/views/aiconfig/AiRoutingList.vue b/web-admin/src/views/aiconfig/AiRoutingList.vue index 34fc2ea..b7cc955 100644 --- a/web-admin/src/views/aiconfig/AiRoutingList.vue +++ b/web-admin/src/views/aiconfig/AiRoutingList.vue @@ -209,6 +209,22 @@ + +
+ + + + + + + + + + 必填 + 删除 +
+ + 添加参数 +
@@ -275,11 +291,20 @@ 接口名称: {{ endpointTestRow?.endpointName }}({{ endpointTestRow?.endpointCode }}) - - - + + + + + + + + + + + (null) const endpointTestResult = ref(null) const endpointNonStreamResult = ref(null) const endpointInputsJson = ref('{}') +const endpointParamFields = ref([]) + +const paramDefinitions = ref([]) const providerForm = reactive(newProvider()) const endpointForm = reactive(newEndpoint()) @@ -441,6 +469,7 @@ function openProvider(row?: AiProvider) { function openEndpoint(row?: AiEndpointConfig) { assignForm(endpointForm, row ? { ...row } : newEndpoint()) + paramDefinitions.value = parseDefinitionsFromDefaultInputs(row?.defaultInputs) endpointDialog.value = true } @@ -465,25 +494,116 @@ function openSceneRuntimeTest(row: AiSceneBinding) { function openEndpointTest(row: AiEndpointConfig) { endpointTestRow.value = row - if (row.defaultInputs) { - try { - const parsed = JSON.parse(row.defaultInputs) - if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) { - endpointInputsJson.value = JSON.stringify(parsed, null, 2) - } else { - endpointInputsJson.value = '{}' - } - } catch { - endpointInputsJson.value = '{}' - } - } else { - endpointInputsJson.value = '{}' - } + endpointParamFields.value = parseParamFields(row.defaultInputs) + endpointInputsJson.value = buildInputsJson(endpointParamFields.value) endpointTestResult.value = null endpointNonStreamResult.value = null endpointTestDialog.value = true } +function parseParamFields(defaultInputs?: string): TestParamField[] { + if (!defaultInputs) return [] + try { + const parsed = JSON.parse(defaultInputs) + if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return [] + return Object.entries(parsed).map(([key, val]: [string, any]) => { + // 新格式:{ _meta: {...}, value: ... } + if (val && typeof val === 'object' && '_meta' in val) { + return { + name: key, + label: val._meta.label || key, + type: val._meta.type || 'string', + value: val.value, + defaultValue: val.value, + required: val._meta.required || false + } as TestParamField + } + // 旧格式:直接值 + return { + name: key, + label: key, + type: inferParamType(val), + value: val, + defaultValue: val, + required: false + } as TestParamField + }) + } catch { + return [] + } +} + +function inferParamType(val: any): 'string' | 'textarea' | 'number' | 'boolean' { + if (typeof val === 'number') return 'number' + if (typeof val === 'boolean') return 'boolean' + if (typeof val === 'string' && val.length > 80) return 'textarea' + return 'string' +} + +function buildInputsJson(fields: TestParamField[]): string { + const obj: Record = {} + fields.forEach(f => { obj[f.name] = f.value }) + return JSON.stringify(obj, null, 2) +} + +function syncEndpointJsonFromFields() { + endpointInputsJson.value = buildInputsJson(endpointParamFields.value) +} + +function syncEndpointFieldsFromJson() { + try { + const parsed = JSON.parse(endpointInputsJson.value || '{}') + endpointParamFields.value.forEach(field => { + if (field.name in parsed) { + field.value = parsed[field.name] + } + }) + } catch { /* ignore parse errors */ } +} + +function addParam() { + paramDefinitions.value.push({ name: '', label: '', type: 'string', defaultValue: '', required: false }) +} + +function removeParam(index: number) { + paramDefinitions.value.splice(index, 1) +} + +function buildDefaultInputsFromDefinitions(defs: ParamDefinition[]): string { + const obj: Record = {} + defs.forEach(d => { + if (d.name) { + obj[d.name] = { + _meta: { label: d.label || d.name, type: d.type, required: d.required }, + value: d.defaultValue + } + } + }) + return JSON.stringify(obj) +} + +function parseDefinitionsFromDefaultInputs(defaultInputs?: string): ParamDefinition[] { + if (!defaultInputs) return [] + try { + const parsed = JSON.parse(defaultInputs) + if (typeof parsed !== 'object' || parsed === null) return [] + return Object.entries(parsed).map(([key, val]: [string, any]) => { + if (val && typeof val === 'object' && '_meta' in val) { + return { + name: key, + label: val._meta.label || key, + type: val._meta.type || 'string', + defaultValue: val.value, + required: val._meta.required || false + } as ParamDefinition + } + return { name: key, label: key, type: inferParamType(val), defaultValue: val, required: false } as ParamDefinition + }) + } catch { + return [] + } +} + async function submitProvider() { await saveAiProvider({ ...providerForm }) providerDialog.value = false @@ -492,7 +612,12 @@ async function submitProvider() { } async function submitEndpoint() { - await saveAiEndpoint({ ...endpointForm }) + const data = { ...endpointForm } + // 如果有参数定义,合并到 defaultInputs + if (paramDefinitions.value.length > 0) { + data.defaultInputs = buildDefaultInputsFromDefinitions(paramDefinitions.value) + } + await saveAiEndpoint(data) endpointDialog.value = false ElMessage.success('接口工作流已保存') await loadAll()