From 138ee140e1ea01a4b9ae4afc7408939fa21c1626 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Sun, 15 Jun 2025 23:42:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai-web):=20=E5=A2=9E=E5=8A=A0feedback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- service-ai/database/service_ai_feedback.sql | 11 ++ .../feedback/FeedbackController.java | 42 +++++++ .../service/ai/web/entity/Feedback.java | 22 ++++ .../web/service/feedback/FeedbackService.java | 81 +++++++++++++ .../ai/web/service/node/EmbeddingNodes.java | 5 +- .../ai/web/service/node/FeedbackNodes.java | 14 +++ .../client/src/pages/ai/feedback/Feedback.tsx | 108 ++++++++++++++++++ service-web/client/src/route.tsx | 38 +++--- service-web/client/src/util/amis.tsx | 4 +- 9 files changed, 301 insertions(+), 24 deletions(-) create mode 100644 service-ai/database/service_ai_feedback.sql create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/feedback/FeedbackController.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Feedback.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/feedback/FeedbackService.java create mode 100644 service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/node/FeedbackNodes.java create mode 100644 service-web/client/src/pages/ai/feedback/Feedback.tsx diff --git a/service-ai/database/service_ai_feedback.sql b/service-ai/database/service_ai_feedback.sql new file mode 100644 index 0000000..db9e438 --- /dev/null +++ b/service-ai/database/service_ai_feedback.sql @@ -0,0 +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 +) 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 new file mode 100644 index 0000000..2615add --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/controller/feedback/FeedbackController.java @@ -0,0 +1,42 @@ +package com.lanyuanxiaoyao.service.ai.web.controller.feedback; + +import cn.hutool.core.util.ObjectUtil; +import com.lanyuanxiaoyao.service.ai.core.entity.amis.AmisResponse; +import com.lanyuanxiaoyao.service.ai.web.service.feedback.FeedbackService; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.collections.api.factory.Lists; +import org.eclipse.collections.api.list.ImmutableList; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("feedback") +public class FeedbackController { + private final FeedbackService feedbackService; + + public FeedbackController(FeedbackService feedbackService) { + this.feedbackService = feedbackService; + } + + @PostMapping("add") + public void add( + @RequestParam("source") String source, + @RequestParam(value = "pictures", required = false) ImmutableList pictures + ) { + feedbackService.add(source, ObjectUtil.defaultIfNull(pictures, Lists.immutable.empty())); + } + + @GetMapping("list") + public AmisResponse list() { + return AmisResponse.responseCrudData(feedbackService.list()); + } + + @GetMapping("delete") + public void delete(@RequestParam("id") Long id) { + feedbackService.remove(id); + } +} 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 new file mode 100644 index 0000000..de0cf3a --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/entity/Feedback.java @@ -0,0 +1,22 @@ +package com.lanyuanxiaoyao.service.ai.web.entity; + +import lombok.Data; +import org.eclipse.collections.api.list.ImmutableList; + +@Data +public class Feedback { + private Long id; + private String source; + private String analysisShort; + private String analysis; + private ImmutableList pictureIds; + private Status status; + private Long createdTime; + private Long modifiedTime; + + public enum Status { + ANALYSIS_PROCESSING, + ANALYSIS_SUCCESS, + FINISHED, + } +} 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 new file mode 100644 index 0000000..36f6a60 --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/feedback/FeedbackService.java @@ -0,0 +1,81 @@ +package com.lanyuanxiaoyao.service.ai.web.service.feedback; + +import club.kingon.sql.builder.SqlBuilder; +import cn.hutool.core.util.EnumUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.lanyuanxiaoyao.service.ai.core.configuration.SnowflakeId; +import com.lanyuanxiaoyao.service.ai.web.entity.Feedback; +import com.lanyuanxiaoyao.service.common.Constants; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.collections.api.factory.Lists; +import org.eclipse.collections.api.list.ImmutableList; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +public class FeedbackService { + public static final String FEEDBACK_TABLE_NAME = Constants.DATABASE_NAME + ".service_ai_feedback"; + 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.setAnalysis(rs.getString(4)); + feedback.setPictureIds( + StrUtil.isBlank(rs.getString(5)) + ? Lists.immutable.empty() + : Lists.immutable.ofAll(StrUtil.split(rs.getString(5), ",")) + ); + feedback.setStatus(EnumUtil.fromString(Feedback.Status.class, rs.getString(6))); + feedback.setCreatedTime(rs.getTimestamp(7).getTime()); + feedback.setModifiedTime(rs.getTimestamp(8).getTime()); + return feedback; + }; + private final JdbcTemplate template; + + public FeedbackService(JdbcTemplate template) { + this.template = template; + } + + @Transactional(rollbackFor = Exception.class) + public void add(String source, ImmutableList pictureIds) { + template.update( + SqlBuilder.insertInto(FEEDBACK_TABLE_NAME, "id", "source", "pictures") + .values() + .addValue("?", "?", "?") + .precompileSql(), + SnowflakeId.next(), + source, + ObjectUtil.isEmpty(pictureIds) + ? null + : pictureIds.makeString(",") + ); + } + + public ImmutableList list() { + return template.query( + SqlBuilder.select("id", "source", "analysis_short", "analysis", "pictures", "status", "created_time", "modified_time") + .from(FEEDBACK_TABLE_NAME) + .orderByDesc("created_time") + .build(), + feedbackMapper + ) + .stream() + .collect(Collectors.toCollection(Lists.mutable::empty)) + .toImmutable(); + } + + @Transactional(rollbackFor = Exception.class) + public void remove(Long id) { + template.update( + SqlBuilder.delete(FEEDBACK_TABLE_NAME) + .whereEq("id", id) + .build() + ); + } +} diff --git a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/node/EmbeddingNodes.java b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/node/EmbeddingNodes.java index 9842180..fd4531f 100644 --- a/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/node/EmbeddingNodes.java +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/node/EmbeddingNodes.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.chat.client.ChatClient; @@ -38,10 +39,9 @@ import org.springframework.core.io.PathResource; * @author lanyuanxiaoyao * @version 20250523 */ +@Slf4j @LiteflowComponent public class EmbeddingNodes { - private static final Logger logger = LoggerFactory.getLogger(EmbeddingNodes.class); - private final ChatClient chatClient; private final QdrantClient qdrantClient; private final EmbeddingModel embeddingModel; @@ -196,7 +196,6 @@ public class EmbeddingNodes { .call() .content(); Assert.notBlank(response, "LLM response is empty"); - logger.info("LLM response: \n{}", response); // noinspection DataFlowIssue return Arrays.stream(StrUtil.trim(response).split("---")) .map(text -> text.replaceAll("(?!^.+) +$", "")) 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 new file mode 100644 index 0000000..02348b1 --- /dev/null +++ b/service-ai/service-ai-web/src/main/java/com/lanyuanxiaoyao/service/ai/web/service/node/FeedbackNodes.java @@ -0,0 +1,14 @@ +package com.lanyuanxiaoyao.service.ai.web.service.node; + +import com.yomahub.liteflow.annotation.LiteflowComponent; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.beans.factory.annotation.Qualifier; + +@LiteflowComponent +public class FeedbackNodes { + private final ChatClient.Builder chatClientBuilder; + + public FeedbackNodes(@Qualifier("chat") ChatClient.Builder chatClientBuilder) { + this.chatClientBuilder = chatClientBuilder; + } +} diff --git a/service-web/client/src/pages/ai/feedback/Feedback.tsx b/service-web/client/src/pages/ai/feedback/Feedback.tsx new file mode 100644 index 0000000..449d49f --- /dev/null +++ b/service-web/client/src/pages/ai/feedback/Feedback.tsx @@ -0,0 +1,108 @@ +import React from 'react' +import {amisRender, commonInfo, crudCommonOptions} from '../../../util/amis.tsx' + +const Feedback: React.FC = () => { + return ( +
+ {amisRender( + { + type: 'page', + title: '报障清单', + body: [ + { + type: 'crud', + api: `${commonInfo.baseAiUrl}/feedback/list`, + ...crudCommonOptions(), + headerToolbar: [ + 'reload', + { + type: 'action', + label: '', + icon: 'fa fa-plus', + tooltip: '新增', + tooltipPlacement: 'top', + actionType: 'dialog', + dialog: { + title: '新增报账单', + size: 'md', + body: { + debug: commonInfo.debug, + type: 'form', + api: { + url: `${commonInfo.baseAiUrl}/feedback/add`, + dataType: 'form', + }, + body: [ + { + type: 'editor', + required: true, + label: '故障描述', + name: 'source', + language: 'plaintext', + options: { + lineNumbers: 'off', + wordWrap: 'bounded', + }, + }, + { + type: 'input-image', + name: 'pictures', + label: '相关截图', + autoUpload: false, + multiple: true, + // 5MB 5242880 + // 100MB 104857600 + // 500MB 524288000 + // 1GB 1073741824 + maxSize: 5242880, + receiver: `${commonInfo.baseAiUrl}/upload` + }, + ], + }, + }, + }, + ], + columns: [ + { + name: 'id', + label: '编号', + }, + { + name: 'status', + label: '状态', + width: 80, + }, + { + type: 'operation', + label: '操作', + width: 150, + buttons: [ + { + type: 'action', + label: '删除', + className: 'text-danger hover:text-red-600', + level: 'link', + size: 'sm', + actionType: 'ajax', + api: { + method: 'get', + url: `${commonInfo.baseAiUrl}/feedback/delete`, + data: { + id: '${id}', + }, + }, + confirmText: '确认删除', + confirmTitle: '删除', + }, + ], + }, + ], + }, + ], + }, + )} +
+ ) +} + +export default Feedback \ No newline at end of file diff --git a/service-web/client/src/route.tsx b/service-web/client/src/route.tsx index 9cefd5a..2f59422 100644 --- a/service-web/client/src/route.tsx +++ b/service-web/client/src/route.tsx @@ -1,20 +1,19 @@ import { - CheckSquareOutlined, - CloudOutlined, - ClusterOutlined, - CompressOutlined, - DatabaseOutlined, - InfoCircleOutlined, - OpenAIOutlined, - QuestionOutlined, - SunOutlined, - SyncOutlined, - TableOutlined, - ToolOutlined, + CheckSquareOutlined, + CloudOutlined, + ClusterOutlined, + CompressOutlined, + DatabaseOutlined, + InfoCircleOutlined, + OpenAIOutlined, + QuestionOutlined, + SunOutlined, + SyncOutlined, + TableOutlined, + ToolOutlined, } from '@ant-design/icons' import {Navigate, type RouteObject} from 'react-router' import Conversation from './pages/ai/Conversation.tsx' -import Inspection from './pages/ai/Inspection.tsx' import DataDetail from './pages/ai/knowledge/DataDetail.tsx' import DataImport from './pages/ai/knowledge/DataImport.tsx' import DataSegment from './pages/ai/knowledge/DataSegment.tsx' @@ -31,6 +30,7 @@ import Yarn from './pages/overview/Yarn.tsx' import YarnCluster from './pages/overview/YarnCluster.tsx' import {commonInfo} from './util/amis.tsx' import Test from './pages/Test.tsx' +import Feedback from './pages/ai/feedback/Feedback.tsx' export const routes: RouteObject[] = [ { @@ -84,14 +84,14 @@ export const routes: RouteObject[] = [ index: true, element: , }, - { - path: 'inspection', - Component: Inspection, - }, { path: 'conversation', Component: Conversation, }, + { + path: 'feedback', + Component: Feedback, + }, { path: 'knowledge', Component: Knowledge, @@ -201,8 +201,8 @@ export const menus = { icon: , }, { - path: '/ai/inspection', - name: '智能巡检', + path: '/ai/feedback', + name: '智慧报账', icon: , }, { diff --git a/service-web/client/src/util/amis.tsx b/service-web/client/src/util/amis.tsx index 4b98e6e..6771bb4 100644 --- a/service-web/client/src/util/amis.tsx +++ b/service-web/client/src/util/amis.tsx @@ -10,8 +10,8 @@ import {isEqual} from 'licia' export const commonInfo = { debug: isEqual(import.meta.env.MODE, 'development'), baseUrl: 'http://132.126.207.130:35690/hudi_services/service_web', - baseAiUrl: 'http://132.126.207.130:35690/hudi_services/service_ai_web', - // baseAiUrl: 'http://localhost:8080', + // baseAiUrl: 'http://132.126.207.130:35690/hudi_services/service_ai_web', + baseAiUrl: 'http://localhost:8080', authorizationHeaders: { 'Authorization': 'Basic QXhoRWJzY3dzSkRiWU1IMjpjWXhnM2I0UHRXb1ZENVNqRmF5V3h0blNWc2p6UnNnNA==', 'Content-Type': 'application/json',