feat(ai-web): 增加feedback
This commit is contained in:
11
service-ai/database/service_ai_feedback.sql
Normal file
11
service-ai/database/service_ai_feedback.sql
Normal 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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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("(?!^.+) +$", ""))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
108
service-web/client/src/pages/ai/feedback/Feedback.tsx
Normal file
108
service-web/client/src/pages/ai/feedback/Feedback.tsx
Normal 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
|
||||
@@ -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/>,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user