feat(ai-web): 增加Feedback详情展示和处理情况确认
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
CREATE TABLE `service_ai_feedback`
|
||||
(
|
||||
`id` bigint NOT NULL,
|
||||
`source` longtext NOT NULL,
|
||||
`analysis_short` longtext,
|
||||
`analysis` longtext,
|
||||
`pictures` longtext,
|
||||
`status` varchar(50) NOT NULL DEFAULT 'ANALYSIS_PROCESSING',
|
||||
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`modified_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
`id` bigint NOT NULL,
|
||||
`source` longtext NOT NULL,
|
||||
`conclusion` longtext,
|
||||
`analysis` longtext,
|
||||
`pictures` longtext,
|
||||
`status` varchar(50) NOT NULL DEFAULT 'ANALYSIS_PROCESSING',
|
||||
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`modified_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) DEFAULT CHARSET = utf8mb4;
|
||||
@@ -45,12 +45,28 @@ public class FeedbackController {
|
||||
feedbackService.remove(id);
|
||||
}
|
||||
|
||||
@GetMapping("reanalysis")
|
||||
public void reanalysis(@RequestParam("id") Long id) {
|
||||
feedbackService.reanalysis(id);
|
||||
}
|
||||
|
||||
@PostMapping("conclude")
|
||||
public void conclude(@RequestBody ConcludeItem item) {
|
||||
feedbackService.updateConclusion(item.getId(), item.getConclusion());
|
||||
}
|
||||
|
||||
@Data
|
||||
public static final class CreateItem {
|
||||
private String source;
|
||||
private ImmutableList<Long> pictures;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static final class ConcludeItem {
|
||||
private Long id;
|
||||
private String conclusion;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static final class ListItem {
|
||||
private Long id;
|
||||
@@ -58,7 +74,7 @@ public class FeedbackController {
|
||||
private ImmutableList<String> pictures;
|
||||
private Feedback.Status status;
|
||||
private String analysis;
|
||||
private String analysisShort;
|
||||
private String conclusion;
|
||||
|
||||
public ListItem(FileStoreProperties fileStoreProperties, Feedback feedback) {
|
||||
this.id = feedback.getId();
|
||||
@@ -67,7 +83,7 @@ public class FeedbackController {
|
||||
.collect(id -> StrUtil.format("{}/upload/download/{}", fileStoreProperties.getDownloadPrefix(), id));
|
||||
this.status = feedback.getStatus();
|
||||
this.analysis = feedback.getAnalysis();
|
||||
this.analysisShort = feedback.getAnalysisShort();
|
||||
this.conclusion = feedback.getConclusion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import org.eclipse.collections.api.list.ImmutableList;
|
||||
public class Feedback {
|
||||
private Long id;
|
||||
private String source;
|
||||
private String analysisShort;
|
||||
private String analysis;
|
||||
private ImmutableList<Long> pictureIds;
|
||||
private String analysis;
|
||||
private String conclusion;
|
||||
private Status status;
|
||||
private Long createdTime;
|
||||
private Long modifiedTime;
|
||||
|
||||
@@ -26,12 +26,12 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Service
|
||||
public class FeedbackService {
|
||||
public static final String FEEDBACK_TABLE_NAME = Constants.DATABASE_NAME + ".service_ai_feedback";
|
||||
public static final String[] FEEDBACK_COLUMNS = new String[]{"id", "source", "analysis_short", "analysis", "pictures", "status", "created_time", "modified_time"};
|
||||
public static final String[] FEEDBACK_COLUMNS = new String[]{"id", "source", "conclusion", "analysis", "pictures", "status", "created_time", "modified_time"};
|
||||
private static final RowMapper<Feedback> feedbackMapper = (rs, row) -> {
|
||||
Feedback feedback = new Feedback();
|
||||
feedback.setId(rs.getLong(1));
|
||||
feedback.setSource(rs.getString(2));
|
||||
feedback.setAnalysisShort(rs.getString(3));
|
||||
feedback.setConclusion(rs.getString(3));
|
||||
feedback.setAnalysis(rs.getString(4));
|
||||
feedback.setPictureIds(
|
||||
StrUtil.isBlank(rs.getString(5))
|
||||
@@ -92,20 +92,32 @@ public class FeedbackService {
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateAnalysis(Long id, String analysis, String analysisShort) {
|
||||
public void updateAnalysis(Long id, String analysis) {
|
||||
Assert.notNull(id, "ID cannot be null");
|
||||
template.update(
|
||||
SqlBuilder.update(FEEDBACK_TABLE_NAME)
|
||||
.set("analysis", "?")
|
||||
.addSet("analysis_short", "?")
|
||||
.whereEq("id", "?")
|
||||
.precompileSql(),
|
||||
analysis,
|
||||
analysisShort,
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateConclusion(Long id, String conclusion) {
|
||||
Assert.notNull(id, "ID cannot be null");
|
||||
template.update(
|
||||
SqlBuilder.update(FEEDBACK_TABLE_NAME)
|
||||
.set("conclusion", "?")
|
||||
.whereEq("id", "?")
|
||||
.precompileSql(),
|
||||
conclusion,
|
||||
id
|
||||
);
|
||||
updateStatus(id, Feedback.Status.FINISHED);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateStatus(Long id, Feedback.Status status) {
|
||||
Assert.notNull(id, "ID cannot be null");
|
||||
@@ -140,4 +152,9 @@ public class FeedbackService {
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void reanalysis(Long id) {
|
||||
updateStatus(id, Feedback.Status.ANALYSIS_PROCESSING);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ public class FeedbackNodes {
|
||||
if (StrUtil.isBlank(optimizedSource)) {
|
||||
log.warn("Optimized source is blank");
|
||||
}
|
||||
context.setOptimizedSource(optimizedSource);
|
||||
context.setOptimizedSource(StrUtil.trim(optimizedSource));
|
||||
}
|
||||
|
||||
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "feedback_suggest", nodeName = "大模型建议", nodeType = NodeTypeEnum.COMMON)
|
||||
@@ -165,29 +165,39 @@ public class FeedbackNodes {
|
||||
Feedback feedback = context.getFeedback();
|
||||
ChatClient client = chatClientBuilder.build();
|
||||
String analysis = client.prompt()
|
||||
// language=TEXT
|
||||
.system("""
|
||||
你是一名专业的IT系统运维工程师,对于用户输入的报障信息,你会给出专业的意见
|
||||
你是一名专业的IT系统运维工程师。针对用户输入的报障信息,严格遵循以下步骤:
|
||||
1. 提取关键要素: 精准识别报障信息中的关键要素(如系统/设备名称、故障现象、错误信息、时间地点、影响范围、用户操作步骤等)。
|
||||
2. 分析诊断: 基于提取的要素,运用运维专业知识,分析潜在原因,并提出优先级高、可操作性强的排障建议或初步诊断方向。
|
||||
3. 输出专业意见:
|
||||
内容要求: 意见必须包含明确的行动步骤(如检查XX日志、验证XX配置、重启XX服务、联系XX团队)、潜在风险提示及预期效果。
|
||||
表达要求: 使用清晰、简洁、专业的技术术语,避免使用推测性语言或不确定词汇(如“可能”、“大概”、“也许”)。
|
||||
格式要求: 直接输出意见内容,避免添加引导语(如“我的建议是”、“根据报障信息”等)。
|
||||
""")
|
||||
.user(context.getOptimizedSource())
|
||||
.call()
|
||||
.content();
|
||||
feedback.setAnalysis(analysis);
|
||||
feedback.setAnalysis(StrUtil.trim(analysis));
|
||||
Assert.notBlank(analysis, "Analysis cannot be blank");
|
||||
String analysisShort = client.prompt()
|
||||
String conclusion = client.prompt()
|
||||
// language=TEXT
|
||||
.system("""
|
||||
你是一名专业的文字编辑,对用户输入的内容,用一段话总结内容
|
||||
""")
|
||||
你是一名专业的文字编辑。严格按以下要求处理用户输入:
|
||||
核心逻辑: 用一段话精准总结输入内容的核心信息,确保涵盖所有关键点。禁止虚构、扩展或添加原文不存在的信息。
|
||||
输出规范: 仅输出一段总结文本。禁止分点、分段、使用Markdown格式(如着重、- 或 1. 等列表符号)或添加任何无关引导语(如“总结如下”、“该总结指出”、“本文总结”等)。直接给出总结文本。""")
|
||||
.user(analysis)
|
||||
.call()
|
||||
.content();
|
||||
feedback.setAnalysisShort(analysisShort);
|
||||
feedback.setConclusion(StrUtil.trim(conclusion));
|
||||
}
|
||||
|
||||
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "feedback_save", nodeName = "保存分析内容", nodeType = NodeTypeEnum.COMMON)
|
||||
public void saveFeedback(NodeComponent node) {
|
||||
FeedbackContext context = node.getContextBean(FeedbackContext.class);
|
||||
Feedback feedback = context.getFeedback();
|
||||
feedbackService.updateAnalysis(feedback.getId(), feedback.getAnalysis(), feedback.getAnalysisShort());
|
||||
feedbackService.updateAnalysis(feedback.getId(), feedback.getAnalysis());
|
||||
feedbackService.updateConclusion(feedback.getId(), feedback.getConclusion());
|
||||
feedbackService.updateStatus(feedback.getId(), Feedback.Status.ANALYSIS_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import {amisRender, commonInfo, crudCommonOptions, mappingField, mappingItem} fr
|
||||
|
||||
const FeedbackDiv = styled.div`
|
||||
.feedback-list-images {
|
||||
margin-top: 10px;
|
||||
|
||||
.antd-Img-container {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
@@ -84,17 +86,24 @@ const Feedback: React.FC = () => {
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
label: '故障描述',
|
||||
},
|
||||
{
|
||||
name: 'pictures',
|
||||
label: '相关截图',
|
||||
width: 200,
|
||||
className: 'feedback-list-images',
|
||||
type: 'images',
|
||||
enlargeAble: true,
|
||||
enlargeWithGallary: true,
|
||||
type: 'flex',
|
||||
direction: 'column',
|
||||
items: [
|
||||
{
|
||||
type: 'tpl',
|
||||
className: 'white-space-pre',
|
||||
tpl: '${source}',
|
||||
},
|
||||
{
|
||||
className: 'feedback-list-images',
|
||||
type: 'images',
|
||||
enlargeAble: true,
|
||||
enlargeWithGallary: true,
|
||||
source: '${pictures}',
|
||||
showToolbar: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
@@ -105,9 +114,107 @@ const Feedback: React.FC = () => {
|
||||
{
|
||||
type: 'operation',
|
||||
label: '操作',
|
||||
width: 150,
|
||||
width: 200,
|
||||
buttons: [
|
||||
{
|
||||
visibleOn: '${status === \'ANALYSIS_SUCCESS\'}',
|
||||
type: 'action',
|
||||
label: '重新分析',
|
||||
level: 'link',
|
||||
size: 'sm',
|
||||
actionType: 'ajax',
|
||||
api: {
|
||||
method: 'get',
|
||||
url: `${commonInfo.baseAiUrl}/feedback/reanalysis`,
|
||||
data: {
|
||||
id: '${id}',
|
||||
},
|
||||
},
|
||||
confirmText: '确认执行重新分析?',
|
||||
confirmTitle: '重新分析',
|
||||
},
|
||||
{
|
||||
disabledOn: '${status === \'ANALYSIS_PROCESSING\'}',
|
||||
type: 'action',
|
||||
label: '详情',
|
||||
level: 'link',
|
||||
size: 'sm',
|
||||
actionType: 'dialog',
|
||||
dialog: {
|
||||
title: '报障详情',
|
||||
size: 'lg',
|
||||
closeOnOutside: true,
|
||||
actions: [
|
||||
{
|
||||
visibleOn: '${status !== \'FINISHED\'}',
|
||||
type: 'action',
|
||||
actionType: 'close',
|
||||
label: '取消',
|
||||
},
|
||||
{
|
||||
visibleOn: '${status !== \'FINISHED\'}',
|
||||
type: 'submit',
|
||||
label: '确认',
|
||||
level: 'primary',
|
||||
},
|
||||
],
|
||||
body: {
|
||||
debug: commonInfo.debug,
|
||||
type: 'form',
|
||||
mode: 'normal',
|
||||
api: {
|
||||
method: 'post',
|
||||
url: `${commonInfo.baseAiUrl}/feedback/conclude`,
|
||||
data: {
|
||||
id: '${id}',
|
||||
conclusion: '${conclusion|default:undefined}',
|
||||
},
|
||||
},
|
||||
body: [
|
||||
{
|
||||
type: 'textarea',
|
||||
name: 'source',
|
||||
label: '报障描述',
|
||||
static: true,
|
||||
},
|
||||
{
|
||||
type: 'control',
|
||||
name: 'pictures',
|
||||
label: '相关截图',
|
||||
body: {
|
||||
type: 'images',
|
||||
enlargeAble: true,
|
||||
enlargeWithGallary: true,
|
||||
showToolbar: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'control',
|
||||
name: 'analysis',
|
||||
label: 'AI辅助分析',
|
||||
body: {
|
||||
type: 'markdown',
|
||||
},
|
||||
},
|
||||
{
|
||||
visibleOn: '${status !== \'FINISHED\'}',
|
||||
type: 'textarea',
|
||||
name: 'conclusion',
|
||||
label: '结论',
|
||||
},
|
||||
{
|
||||
visibleOn: '${status === \'FINISHED\'}',
|
||||
type: 'textarea',
|
||||
name: 'conclusion',
|
||||
label: '结论',
|
||||
static: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
disabledOn: '${status === \'ANALYSIS_PROCESSING\'}',
|
||||
type: 'action',
|
||||
label: '删除',
|
||||
className: 'text-danger hover:text-red-600',
|
||||
@@ -121,8 +228,8 @@ const Feedback: React.FC = () => {
|
||||
id: '${id}',
|
||||
},
|
||||
},
|
||||
confirmText: '确认删除',
|
||||
confirmTitle: '删除',
|
||||
confirmText: '删除后将无法恢复,确认删除?',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user