From e89bffe289753d72ce9743e99bed1cf130b8b9c2 Mon Sep 17 00:00:00 2001 From: v-zhangjc9 Date: Tue, 17 Jun 2025 16:15:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai-web):=20=E5=A2=9E=E5=8A=A0Feedback?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E5=B1=95=E7=A4=BA=E5=92=8C=E5=A4=84=E7=90=86?= =?UTF-8?q?=E6=83=85=E5=86=B5=E7=A1=AE=E8=AE=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- service-ai/database/service_ai_feedback.sql | 16 +-- .../feedback/FeedbackController.java | 20 ++- .../service/ai/web/entity/Feedback.java | 4 +- .../web/service/feedback/FeedbackService.java | 27 +++- .../ai/web/service/node/FeedbackNodes.java | 26 ++-- .../client/src/pages/ai/feedback/Feedback.tsx | 131 ++++++++++++++++-- 6 files changed, 187 insertions(+), 37 deletions(-) diff --git a/service-ai/database/service_ai_feedback.sql b/service-ai/database/service_ai_feedback.sql index db9e438..f9ce3ba 100644 --- a/service-ai/database/service_ai_feedback.sql +++ b/service-ai/database/service_ai_feedback.sql @@ -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; \ No newline at end of file diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/feedback/FeedbackController.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/feedback/FeedbackController.java index d0f1bd5..ec083db 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/feedback/FeedbackController.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/feedback/FeedbackController.java @@ -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 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 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(); } } } diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Feedback.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Feedback.java index c795282..0e6ee77 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Feedback.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Feedback.java @@ -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 pictureIds; + private String analysis; + private String conclusion; private Status status; private Long createdTime; private Long modifiedTime; diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/feedback/FeedbackService.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/feedback/FeedbackService.java index a0dd029..8d23e78 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/feedback/FeedbackService.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/feedback/FeedbackService.java @@ -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 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); + } } diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/node/FeedbackNodes.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/node/FeedbackNodes.java index 769b36d..6a4a239 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/node/FeedbackNodes.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/node/FeedbackNodes.java @@ -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); } } diff --git a/service-web/client/src/pages/ai/feedback/Feedback.tsx b/service-web/client/src/pages/ai/feedback/Feedback.tsx index 82c6487..307571f 100644 --- a/service-web/client/src/pages/ai/feedback/Feedback.tsx +++ b/service-web/client/src/pages/ai/feedback/Feedback.tsx @@ -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: '删除后将无法恢复,确认删除?', }, ], },