From fc54bcd3d63176a1d2a0db8ab4fc90a0b5d3da99 Mon Sep 17 00:00:00 2001 From: 2910410219 <2910410219@qq.com> Date: Sat, 6 Jun 2026 13:46:31 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E6=B5=81=E5=BC=8F=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=BC=B9=E7=AA=97=EF=BC=8C=E6=96=B0=E5=A2=9E=E6=B5=81?= =?UTF-8?q?=E5=BC=8F=E5=8F=82=E6=95=B0=E8=A1=A8=E5=8D=95=E5=92=8C=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E9=80=BB=E8=BE=91=EF=BC=8C=E7=A7=BB=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E6=A8=A1=E5=9E=8B=E7=B1=BB=E5=9E=8B=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modelModule/component/editModule.vue | 381 +++++++++++++++++- .../settings/modelConfig/modelType/index.vue | 127 ------ 2 files changed, 375 insertions(+), 133 deletions(-) delete mode 100644 src/views/settings/modelConfig/modelType/index.vue diff --git a/src/views/settings/modelConfig/modelModule/component/editModule.vue b/src/views/settings/modelConfig/modelModule/component/editModule.vue index f4f4ac9..ebc4454 100644 --- a/src/views/settings/modelConfig/modelModule/component/editModule.vue +++ b/src/views/settings/modelConfig/modelModule/component/editModule.vue @@ -241,14 +241,53 @@ - -
- 流式配置表单内容待确定,当前将按空对象提交。 -
+ + +
+
+
内容路径
+ + + +
+
+
+
+
+ {{ templateKey }} 附件模板 +
+
+
type
+ + + +
body
+
+
+
+ + = + + 删除 +
+ + 添加 body 字段 +
+
+
+
+
+
@@ -554,6 +593,20 @@ export interface ResponseMappingField extends KeyValueField { isTokenField?: boolean; } +export interface StreamAttachmentTemplateForm { + type: string; + bodyFields: KeyValueField[]; +} + +export interface StreamConfigForm { + targetContentPath: string; + attachmentTemplates: { + audio: StreamAttachmentTemplateForm; + image: StreamAttachmentTemplateForm; + video: StreamAttachmentTemplateForm; + }; +} + const props = withDefaults( defineProps<{ modelTypes?: ModelTypeOption[]; @@ -587,6 +640,7 @@ const typeOptionValue = (id: number | string): number | string => { }; const editModuleFormRef = ref(); +const streamConfigDialogFormRef = ref(); const emit = defineEmits(['refresh']); const showHeaderDialog = ref(false); const showAsyncQueryConfigDialog = ref(false); @@ -606,6 +660,29 @@ const asyncQueryConfigForm = reactive({ statusValueFields: [{ key: '', value: '' }] as Array<{ key: string; value: string }>, }); +const createEmptyStreamAttachmentTemplate = (): StreamAttachmentTemplateForm => ({ + type: '', + bodyFields: [{ key: '', value: '' }], +}); + +const createEmptyStreamConfigForm = (): StreamConfigForm => ({ + targetContentPath: '', + attachmentTemplates: { + audio: createEmptyStreamAttachmentTemplate(), + image: createEmptyStreamAttachmentTemplate(), + video: createEmptyStreamAttachmentTemplate(), + }, +}); + +const streamConfigForm = reactive(createEmptyStreamConfigForm()); +const streamConfigRules = { + targetContentPath: [{ required: true, message: '请输入内容路径', trigger: 'blur' }], + audioType: [{ required: true, message: '请输入 Audio 附件模板 type', trigger: 'blur' }], + imageType: [{ required: true, message: '请输入 Image 附件模板 type', trigger: 'blur' }], + videoType: [{ required: true, message: '请输入 Video 附件模板 type', trigger: 'blur' }], +}; +const streamAttachmentTemplateKeys: Array = ['audio', 'image', 'video']; + const state = reactive({ ruleForm: { id: '', @@ -703,6 +780,50 @@ const state = reactive({ trigger: 'change', }, ], + streamConfig: [ + { + validator: (_rule: unknown, _value: unknown, callback: (e?: Error) => void) => { + if (Number(state.ruleForm.callMode) !== 2) { + callback(); + return; + } + if (!String(streamConfigForm.targetContentPath || '').trim()) { + callback(new Error('流式执行时,请填写内容路径')); + return; + } + const missingTemplateType = Object.entries(streamConfigForm.attachmentTemplates).some( + ([, template]) => !String(template.type || '').trim() + ); + if (missingTemplateType) { + callback(new Error('流式执行时,请完整填写所有附件模板 type')); + return; + } + const hasEmptyBodyTemplate = Object.entries(streamConfigForm.attachmentTemplates).some(([, template]) => { + const validRows = template.bodyFields.filter( + (item) => String(item.key || '').trim() !== '' && String(item.value || '').trim() !== '' + ); + return validRows.length === 0; + }); + if (hasEmptyBodyTemplate) { + callback(new Error('流式执行时,每个附件模板至少需要一个 body 字段')); + return; + } + const hasInvalidBodyField = Object.entries(streamConfigForm.attachmentTemplates).some(([, template]) => + template.bodyFields.some( + (item) => + (String(item.key || '').trim() === '' && String(item.value || '').trim() !== '') || + (String(item.key || '').trim() !== '' && String(item.value || '').trim() === '') + ) + ); + if (hasInvalidBodyField) { + callback(new Error('附件模板 body 的键和值都必须完整填写')); + return; + } + callback(); + }, + trigger: 'change', + }, + ], operatorName: [ { validator: (_rule: unknown, value: unknown, callback: (e?: Error) => void) => { @@ -1027,14 +1148,90 @@ const addAsyncQueryStatusValueField = () => { asyncQueryConfigForm.statusValueFields.push({ key: '', value: '' }); }; +const addStreamBodyField = (templateKey: keyof StreamConfigForm['attachmentTemplates']) => { + streamConfigForm.attachmentTemplates[templateKey].bodyFields.push({ key: '', value: '' }); +}; + +const removeStreamBodyField = (templateKey: keyof StreamConfigForm['attachmentTemplates'], index: number) => { + const template = streamConfigForm.attachmentTemplates[templateKey]; + if (template.bodyFields.length <= 1) { + template.bodyFields[0] = { key: '', value: '' }; + return; + } + template.bodyFields.splice(index, 1); +}; + const removeAsyncQueryStatusValueField = (index: number) => { asyncQueryConfigForm.statusValueFields.splice(index, 1); }; +const validateAndConfirmStreamConfig = async () => { + await streamConfigDialogFormRef.value?.validate?.(); + await editModuleFormRef.value?.validateField?.('streamConfig'); + showStreamConfigDialog.value = false; +}; + const objectToFields = (obj: Record) => { return Object.entries(obj).map(([key, value]) => ({ key, value: String(value ?? '') })); }; +const resetStreamConfigForm = () => { + const next = createEmptyStreamConfigForm(); + streamConfigForm.targetContentPath = next.targetContentPath; + streamConfigForm.attachmentTemplates.audio.type = next.attachmentTemplates.audio.type; + streamConfigForm.attachmentTemplates.audio.bodyFields = next.attachmentTemplates.audio.bodyFields; + streamConfigForm.attachmentTemplates.image.type = next.attachmentTemplates.image.type; + streamConfigForm.attachmentTemplates.image.bodyFields = next.attachmentTemplates.image.bodyFields; + streamConfigForm.attachmentTemplates.video.type = next.attachmentTemplates.video.type; + streamConfigForm.attachmentTemplates.video.bodyFields = next.attachmentTemplates.video.bodyFields; +}; + +const applyStreamAttachmentTemplate = (template: StreamAttachmentTemplateForm, raw: unknown) => { + template.type = ''; + template.bodyFields = [{ key: '', value: '' }]; + if (!raw || typeof raw !== 'object' || Array.isArray(raw)) { + return; + } + const rawTemplate = raw as Record; + template.type = String(rawTemplate.type || ''); + template.bodyFields = ensureKeyValueRows(parseFieldsUnified(rawTemplate.body), () => ({ key: '', value: '' })); +}; + +const applyStreamConfig = (raw: unknown) => { + resetStreamConfigForm(); + if (!raw || typeof raw !== 'object' || Array.isArray(raw)) { + return; + } + const streamConfig = raw as Record; + streamConfigForm.targetContentPath = String(streamConfig.target_content_path || ''); + const attachmentTemplates = + streamConfig.attachment_templates && typeof streamConfig.attachment_templates === 'object' && !Array.isArray(streamConfig.attachment_templates) + ? (streamConfig.attachment_templates as Record) + : {}; + applyStreamAttachmentTemplate(streamConfigForm.attachmentTemplates.audio, attachmentTemplates.audio); + applyStreamAttachmentTemplate(streamConfigForm.attachmentTemplates.image, attachmentTemplates.image); + applyStreamAttachmentTemplate(streamConfigForm.attachmentTemplates.video, attachmentTemplates.video); +}; + +const buildStreamAttachmentTemplate = (template: StreamAttachmentTemplateForm) => ({ + type: String(template.type || '').trim(), + body: fieldsToUnknownObject(template.bodyFields.filter((item) => String(item.key || '').trim() !== '')), +}); + +const buildStreamConfig = () => { + if (Number(state.ruleForm.callMode) !== 2) { + return undefined; + } + return { + target_content_path: String(streamConfigForm.targetContentPath || '').trim(), + attachment_templates: { + audio: buildStreamAttachmentTemplate(streamConfigForm.attachmentTemplates.audio), + image: buildStreamAttachmentTemplate(streamConfigForm.attachmentTemplates.image), + video: buildStreamAttachmentTemplate(streamConfigForm.attachmentTemplates.video), + }, + }; +}; + const buildQueryConfig = () => { if (Number(state.ruleForm.callMode) === 1) { return buildAsyncQueryConfig(); @@ -1371,6 +1568,8 @@ const fillFormFromDetailRow = (row: Record) => { asyncQueryConfigForm.intervalSeconds = 2; asyncQueryConfigForm.statusValueFields = [{ key: '', value: '' }]; } + + applyStreamConfig(row.streamConfig); }; // 打开弹窗(编辑时会请求 /model/getModel 详情) const openDialog = async (type: string, row?: Record) => { @@ -1451,6 +1650,7 @@ const openDialog = async (type: string, row?: Record) => { asyncQueryConfigForm.statusPath = ''; asyncQueryConfigForm.intervalSeconds = 2; asyncQueryConfigForm.statusValueFields = [{ key: '', value: '' }]; + resetStreamConfigForm(); state.dialog.title = '新增模型配置'; state.dialog.submitTxt = '新 增'; } @@ -1479,6 +1679,9 @@ const onSubmit = () => { if (Number(state.ruleForm.callMode) === 1) { await editModuleFormRef.value?.validateField?.('asyncQueryConfig'); } + if (Number(state.ruleForm.callMode) === 2) { + await editModuleFormRef.value?.validateField?.('streamConfig'); + } // 验证响应映射(如果有配置) const validResponseFields = state.responseMappingFields.filter((x) => String(x.key || '').trim() !== ''); @@ -1572,7 +1775,7 @@ const onSubmit = () => { responseTokenField, tokenConfig: fieldsToUnknownObject(state.tokenConfigFields.filter((f) => String(f.key || '').trim() !== '')), queryConfig: buildQueryConfig(), - streamConfig: Number(state.ruleForm.callMode) === 2 ? {} : undefined, + streamConfig: buildStreamConfig(), }; if (state.dialog.type === 'edit') { @@ -1718,6 +1921,172 @@ onMounted(() => { } } +.stream-dialog-form { + padding-top: 4px; +} + +.stream-dialog-panel { + padding: 14px 16px; + border: 1px solid #ebeef5; + border-radius: 8px; + background: #fcfcfd; + margin-bottom: 14px; + + &__row { + display: grid; + grid-template-columns: 96px minmax(0, 1fr); + align-items: start; + column-gap: 12px; + row-gap: 6px; + } + + &__label { + height: 32px; + line-height: 32px; + font-size: 13px; + color: #606266; + padding-top: 1px; + + &.required::before { + content: '*'; + color: #f56c6c; + margin-right: 4px; + } + } + + &__item { + margin-bottom: 2px; + + :deep(.el-form-item__content) { + line-height: 32px; + } + } + + &__item--full { + width: 100%; + } +} + +.stream-config-container { + display: flex; + flex-direction: column; + gap: 12px; +} + +.stream-template-card { + padding: 14px 16px; + border: 1px solid #ebeef5; + border-radius: 8px; + background: #fcfcfd; + + &__header { + margin-bottom: 10px; + } + + &__title { + font-size: 14px; + font-weight: 600; + color: #303133; + text-transform: capitalize; + } +} + +.stream-template-grid { + display: grid; + grid-template-columns: 96px minmax(0, 1fr); + column-gap: 12px; + row-gap: 10px; + align-items: start; + + &__label { + height: 32px; + line-height: 32px; + font-size: 13px; + color: #606266; + padding-top: 1px; + + &.required::before { + content: '*'; + color: #f56c6c; + margin-right: 4px; + } + } + + &__label--top { + padding-top: 2px; + height: auto; + line-height: 20px; + } + + &__item { + margin-bottom: 2px; + + :deep(.el-form-item__content) { + line-height: 32px; + } + } + + &__item--full { + width: 100%; + } +} + +.stream-body-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.stream-body-row { + display: grid; + grid-template-columns: minmax(0, 1fr) 20px minmax(0, 1fr) 44px; + column-gap: 8px; + align-items: center; + + &__input { + width: 100%; + } + + &__separator { + text-align: center; + font-weight: 600; + color: #606266; + } + + &__delete { + justify-self: end; + padding: 0; + } +} + +.stream-body-list__add { + align-self: flex-start; + padding: 0; +} + +:deep(.stream-dialog-form .el-input__wrapper) { + min-height: 32px; +} + +:deep(.stream-dialog-form .el-input__inner) { + height: 32px; +} + +:deep(.stream-dialog-form .el-form-item) { + margin-bottom: 0; +} + +:deep(.stream-dialog-form .el-form-item__content) { + display: block; +} + +:deep(.stream-dialog-form .el-form-item__error) { + position: static; + padding-top: 4px; + line-height: 1.2; + white-space: normal; +} + .ml5 { margin-left: 5px; } diff --git a/src/views/settings/modelConfig/modelType/index.vue b/src/views/settings/modelConfig/modelType/index.vue deleted file mode 100644 index 5e24f96..0000000 --- a/src/views/settings/modelConfig/modelType/index.vue +++ /dev/null @@ -1,127 +0,0 @@ - - - - -