feat(ai-web): 增加feedback

This commit is contained in:
2025-06-15 23:42:26 +08:00
parent e2d69bc6e8
commit 138ee140e1
9 changed files with 301 additions and 24 deletions

View File

@@ -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;

View File

@@ -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<Long> 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);
}
}

View File

@@ -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<String> pictureIds;
private Status status;
private Long createdTime;
private Long modifiedTime;
public enum Status {
ANALYSIS_PROCESSING,
ANALYSIS_SUCCESS,
FINISHED,
}
}

View File

@@ -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<Feedback> 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<Long> 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<Feedback> 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()
);
}
}

View File

@@ -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("(?!^.+) +$", ""))

View File

@@ -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;
}
}

View File

@@ -0,0 +1,108 @@
import React from 'react'
import {amisRender, commonInfo, crudCommonOptions} from '../../../util/amis.tsx'
const Feedback: React.FC = () => {
return (
<div className="feedback">
{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: '删除',
},
],
},
],
},
],
},
)}
</div>
)
}
export default Feedback

View File

@@ -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: <Navigate to="/ai/conversation" replace/>,
},
{
path: 'inspection',
Component: Inspection,
},
{
path: 'conversation',
Component: Conversation,
},
{
path: 'feedback',
Component: Feedback,
},
{
path: 'knowledge',
Component: Knowledge,
@@ -201,8 +201,8 @@ export const menus = {
icon: <QuestionOutlined/>,
},
{
path: '/ai/inspection',
name: '智能巡检',
path: '/ai/feedback',
name: '智慧报账',
icon: <CheckSquareOutlined/>,
},
{

View File

@@ -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',