feat(ai-web): 完成任务模板的CRUD

This commit is contained in:
v-zhangjc9
2025-07-03 18:01:36 +08:00
parent 064443f740
commit 5f7eeb3596
3 changed files with 195 additions and 41 deletions

View File

@@ -1,13 +1,22 @@
package com.lanyuanxiaoyao.service.ai.web.controller.task; package com.lanyuanxiaoyao.service.ai.web.controller.task;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lanyuanxiaoyao.service.ai.core.entity.amis.AmisCrudResponse;
import com.lanyuanxiaoyao.service.ai.core.entity.amis.AmisResponse;
import com.lanyuanxiaoyao.service.ai.web.base.controller.SimpleControllerSupport; import com.lanyuanxiaoyao.service.ai.web.base.controller.SimpleControllerSupport;
import com.lanyuanxiaoyao.service.ai.web.base.controller.query.Query;
import com.lanyuanxiaoyao.service.ai.web.base.entity.SimpleItem; import com.lanyuanxiaoyao.service.ai.web.base.entity.SimpleItem;
import com.lanyuanxiaoyao.service.ai.web.entity.FlowTaskTemplate; import com.lanyuanxiaoyao.service.ai.web.entity.FlowTaskTemplate;
import com.lanyuanxiaoyao.service.ai.web.service.task.FlowTaskTemplateService; import com.lanyuanxiaoyao.service.ai.web.service.task.FlowTaskTemplateService;
import java.util.Map;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.mapstruct.Context;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@@ -15,13 +24,32 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("flow_task/template") @RequestMapping("flow_task/template")
public class TaskTemplateController extends SimpleControllerSupport<FlowTaskTemplate, TaskTemplateController.SaveItem, TaskTemplateController.ListItem, TaskTemplateController.DetailItem> { public class TaskTemplateController extends SimpleControllerSupport<FlowTaskTemplate, TaskTemplateController.SaveItem, TaskTemplateController.ListItem, TaskTemplateController.DetailItem> {
public TaskTemplateController(FlowTaskTemplateService flowTaskTemplateService) { private final ObjectMapper mapper;
public TaskTemplateController(FlowTaskTemplateService flowTaskTemplateService, Jackson2ObjectMapperBuilder builder) {
super(flowTaskTemplateService); super(flowTaskTemplateService);
this.mapper = builder.build();
}
@Override
public AmisResponse<Long> save(SaveItem saveItem) throws Exception {
log.info("Save: {}", saveItem);
SaveItem.Mapper map = Mappers.getMapper(SaveItem.Mapper.class);
log.info("Mapper: {}", map.from(saveItem, mapper));
return super.save(saveItem);
}
@Override
public AmisCrudResponse list(Query query) throws Exception {
AmisCrudResponse list = super.list(query);
log.info("List: {}", list);
return list;
} }
@Override @Override
protected SaveItemMapper<FlowTaskTemplate, SaveItem> saveItemMapper() { protected SaveItemMapper<FlowTaskTemplate, SaveItem> saveItemMapper() {
return Mappers.getMapper(SaveItem.Mapper.class); SaveItem.Mapper map = Mappers.getMapper(SaveItem.Mapper.class);
return item -> map.from(item, mapper);
} }
@Override @Override
@@ -31,18 +59,24 @@ public class TaskTemplateController extends SimpleControllerSupport<FlowTaskTemp
@Override @Override
protected DetailItemMapper<FlowTaskTemplate, DetailItem> detailItemMapper() { protected DetailItemMapper<FlowTaskTemplate, DetailItem> detailItemMapper() {
return Mappers.getMapper(DetailItem.Mapper.class); DetailItem.Mapper map = Mappers.getMapper(DetailItem.Mapper.class);
return template -> map.from(template, mapper);
} }
@Data @Data
public static final class SaveItem { public static final class SaveItem {
private Long id;
private String name; private String name;
private String description; private String description;
private String inputSchema; private Map<String, Object> inputSchema;
private String flow;
@org.mapstruct.Mapper @org.mapstruct.Mapper
public interface Mapper extends SaveItemMapper<FlowTaskTemplate, SaveItem> { public static abstract class Mapper {
public abstract FlowTaskTemplate from(SaveItem saveItem, @Context ObjectMapper mapper) throws Exception;
protected String mapInputSchema(Map<String, Object> inputSchema, @Context ObjectMapper mapper) throws JsonProcessingException {
return mapper.writeValueAsString(inputSchema);
}
} }
} }
@@ -62,11 +96,15 @@ public class TaskTemplateController extends SimpleControllerSupport<FlowTaskTemp
public static class DetailItem extends SimpleItem { public static class DetailItem extends SimpleItem {
private String name; private String name;
private String description; private String description;
private String inputSchema; private Map<String, Object> inputSchema;
private String flow;
@org.mapstruct.Mapper @org.mapstruct.Mapper
public interface Mapper extends DetailItemMapper<FlowTaskTemplate, DetailItem> { public static abstract class Mapper {
public abstract DetailItem from(FlowTaskTemplate template, @Context ObjectMapper mapper) throws Exception;
public Map<String, Object> mapInputSchema(String inputSchema, @Context ObjectMapper mapper) throws Exception {
return mapper.readValue(inputSchema, new TypeReference<>() {});
}
} }
} }
} }

View File

@@ -0,0 +1,48 @@
import type {Schema} from 'amis'
export const typeMap: Record<string, string> = {
text: '文本',
number: '数字',
files: '文件',
}
export type InputField = {
type: string
label: string
description?: string
}
export const generateInputForm: (inputSchema: Record<string, InputField>) => Schema = inputSchema => {
let items: Schema[] = []
for (const name of Object.keys(inputSchema)) {
let field = inputSchema[name]
// @ts-ignore
let formItem: Schema = {
name: name,
...field,
}
switch (field.type) {
case 'text':
formItem = {
...formItem,
type: 'input-text',
clearValueOnEmpty: true,
}
break
case 'number':
formItem.type = 'input-number'
break
case 'files':
formItem.type = 'input-file'
break
}
items.push(formItem)
}
return {
type: 'form',
title: '入参表单预览',
canAccessSuperData: false,
actions: [],
body: items,
}
}

View File

@@ -1,8 +1,9 @@
import {isEmpty, isEqual} from 'licia'
import React from 'react' import React from 'react'
import {useParams} from 'react-router'
import styled from 'styled-components' import styled from 'styled-components'
import {amisRender, commonInfo, horizontalFormOptions} from '../../../../util/amis.tsx' import {amisRender, commonInfo, horizontalFormOptions} from '../../../../util/amis.tsx'
import {isEqual} from 'licia' import {generateInputForm, typeMap} from '../InputSchema.tsx'
import { useParams } from 'react-router'
const TemplateEditDiv = styled.div` const TemplateEditDiv = styled.div`
.antd-EditorControl { .antd-EditorControl {
@@ -13,7 +14,6 @@ const TemplateEditDiv = styled.div`
const FlowTaskTemplateEdit: React.FC = () => { const FlowTaskTemplateEdit: React.FC = () => {
const {template_id} = useParams() const {template_id} = useParams()
const preloadTemplateId = isEqual(template_id, '-1') ? undefined : template_id const preloadTemplateId = isEqual(template_id, '-1') ? undefined : template_id
console.log('preloadTemplateId', preloadTemplateId)
return ( return (
<TemplateEditDiv className="task-template-edit h-full"> <TemplateEditDiv className="task-template-edit h-full">
{amisRender({ {amisRender({
@@ -26,62 +26,130 @@ const FlowTaskTemplateEdit: React.FC = () => {
method: 'POST', method: 'POST',
url: `${commonInfo.baseAiUrl}/flow_task/template/save`, url: `${commonInfo.baseAiUrl}/flow_task/template/save`,
data: { data: {
name: '${template.name}', id: '${id|default:undefined}',
description: '${template.description}', name: '${name}',
inputSchema: '${template.inputSchema}', description: '${description}',
flow: '${template.flow}', inputSchema: '${inputSchema|default:undefined}',
} },
}, },
initApi: preloadTemplateId initApi: preloadTemplateId
? { ? {
method: 'GET', method: 'GET',
url: `${commonInfo.baseAiUrl}/flow_task/template/detail/${preloadTemplateId}`, url: `${commonInfo.baseAiUrl}/flow_task/template/detail/${preloadTemplateId}`,
// @ts-ignore
adaptor: (payload, response, api, context) => {
return {
...payload,
data: {
template: payload.data,
},
}
},
} }
: undefined, : undefined,
wrapWithPanel: false, wrapWithPanel: false,
...horizontalFormOptions(), ...horizontalFormOptions(),
onEvent: {
change: {
actions: [
{
actionType: 'validate',
},
{
actionType: 'custom',
// @ts-ignore
script: (context, doAction, event) => {
let data = event?.data ?? {}
let inputSchema = data.inputSchema ?? []
if (!isEmpty(inputSchema) && isEmpty(data?.validateResult?.error ?? undefined)) {
doAction({
actionType: 'setValue',
args: {
value: {
inputPreview: generateInputForm(inputSchema),
},
},
})
}
},
},
],
},
},
body: [ body: [
{
type: 'hidden',
name: 'id',
},
{ {
type: 'input-text', type: 'input-text',
name: 'template.name', name: 'name',
label: '名称', label: '名称',
required: true, required: true,
clearable: true,
}, },
{ {
type: 'textarea', type: 'textarea',
name: 'template.description', name: 'description',
label: '描述', label: '描述',
required: true, required: true,
clearable: true,
}, },
{ {
type: 'group', type: 'group',
body: [ body: [
{ {
type: 'editor', type: 'wrapper',
required: true, size: 'none',
label: '入参表单', body: [
description: '使用amis代码编写入参表单结构流程会解析所有name对应的变量传入流程开始阶段只需要编写form的columns部分', {
name: 'template.inputSchema', type: 'input-kvs',
value: '[]', name: 'inputSchema',
language: 'json', label: '输入变量',
options: { addButtonText: '新增入参',
wordWrap: 'bounded', draggable: false,
}, keyItem: {
label: '参数名称',
...horizontalFormOptions(),
validations: {
isAlphanumeric: true,
},
},
valueItems: [
{
...horizontalFormOptions(),
type: 'input-text',
name: 'label',
required: true,
label: '中文名称',
clearValueOnEmpty: true,
clearable: true,
},
{
...horizontalFormOptions(),
type: 'input-text',
name: 'description',
label: '参数描述',
clearValueOnEmpty: true,
clearable: true,
},
{
...horizontalFormOptions(),
type: 'select',
name: 'type',
label: '参数类型',
required: true,
selectFirst: true,
options: Object.keys(typeMap).map(key => ({label: typeMap[key], value: key})),
},
{
...horizontalFormOptions(),
type: 'switch',
name: 'required',
label: '是否必填',
required: true,
value: true,
},
],
},
],
}, },
{ {
type: 'amis', type: 'amis',
name: 'template.inputSchema', name: 'inputPreview',
} },
] ],
}, },
{ {
type: 'button-toolbar', type: 'button-toolbar',