#AI
Junjie
14 小时以前 70af762aa04fccfd68f211a9991c9a36f0228dfd
#AI
2个文件已添加
4个文件已修改
269 ■■■■■ 已修改文件
src/main/java/com/zy/ai/controller/WcsDiagnosisController.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/entity/ChatCompletionRequest.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/service/LlmChatService.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/service/WcsDiagnosisService.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/ai/diagnose.js 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/ai/diagnose.html 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/controller/WcsDiagnosisController.java
@@ -22,6 +22,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.ArrayList;
import java.util.HashMap;
@@ -47,7 +48,7 @@
        request.setAlarmMessage("系统不执行任务");
        List<String> logs = AiLogAppender.getRecentLogs(300);
        List<String> logs = AiLogAppender.getRecentLogs(100);
        request.setLogs(logs);
        List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<>());
@@ -99,8 +100,81 @@
        }
        request.setDeviceRealtimeData(deviceRealTimeDataList);
        request.setDeviceConfigs(deviceConfigsDataList);
        WcsDiagnosisResponse response = diagnose(request);
        return response;
    }
    @GetMapping("/runAiStream")
    public SseEmitter runAiStream() {
        SseEmitter emitter = new SseEmitter(0L);
        new Thread(() -> {
            try {
                WcsDiagnosisRequest request = new WcsDiagnosisRequest();
                request.setAlarmMessage("系统不执行任务");
                List<String> logs = AiLogAppender.getRecentLogs(100);
                request.setLogs(logs);
                List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<>());
                request.setTasks(wrkMasts);
                List<DeviceRealTimeData> deviceRealTimeDataList = new ArrayList<>();
                List<DeviceConfigsData> deviceConfigsDataList = new ArrayList<>();
                List<BasCrnp> basCrnps = basCrnpService.selectList(new EntityWrapper<>());
                for (BasCrnp basCrnp : basCrnps) {
                    CrnThread crnThread = (CrnThread) SlaveConnection.get(SlaveType.Crn, basCrnp.getCrnNo());
                    if (crnThread == null) {
                        continue;
                    }
                    CrnProtocol protocol = crnThread.getStatus();
                    for (StationObjModel stationObjModel : basCrnp.getInStationList$()) {
                        StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
                        if (stationThread == null) {
                            continue;
                        }
                        Map<Integer, StationProtocol> map = stationThread.getStatusMap();
                        StationProtocol stationProtocol = map.get(stationObjModel.getStationId());
                        if (stationProtocol == null) {
                            continue;
                        }
                        DeviceRealTimeData stationData = new DeviceRealTimeData();
                        stationData.setDeviceNo(stationObjModel.getDeviceNo());
                        stationData.setDeviceType(String.valueOf(SlaveType.Devp));
                        stationData.setDeviceData(stationProtocol);
                        deviceRealTimeDataList.add(stationData);
                    }
                    DeviceRealTimeData deviceRealTimeData = new DeviceRealTimeData();
                    deviceRealTimeData.setDeviceNo(basCrnp.getCrnNo());
                    deviceRealTimeData.setDeviceType(String.valueOf(SlaveType.Crn));
                    deviceRealTimeData.setDeviceData(protocol);
                    deviceRealTimeDataList.add(deviceRealTimeData);
                    DeviceConfigsData deviceConfigsData = new DeviceConfigsData();
                    deviceConfigsData.setDeviceNo(basCrnp.getCrnNo());
                    deviceConfigsData.setDeviceType(String.valueOf(SlaveType.Crn));
                    deviceConfigsData.setDeviceData(basCrnp);
                    deviceConfigsDataList.add(deviceConfigsData);
                }
                request.setDeviceRealtimeData(deviceRealTimeDataList);
                request.setDeviceConfigs(deviceConfigsDataList);
                wcsDiagnosisService.diagnoseStream(request, emitter);
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        }).start();
        return emitter;
    }
    /**
@@ -115,4 +189,4 @@
        resp.setOriginalRequest(request);
        return resp;
    }
}
}
src/main/java/com/zy/ai/entity/ChatCompletionRequest.java
@@ -12,10 +12,11 @@
    // 可选参数
    private Double temperature;
    private Integer max_tokens;
    private Boolean stream;
    @Data
    public static class Message {
        private String role;    // "user" / "assistant" / "system"
        private String content;
    }
}
}
src/main/java/com/zy/ai/service/LlmChatService.java
@@ -10,8 +10,13 @@
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;
import java.util.List;
import java.util.function.Consumer;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
@Slf4j
@Service
@@ -60,4 +65,52 @@
        return response.getChoices().get(0).getMessage().getContent();
    }
}
    public void chatStream(List<ChatCompletionRequest.Message> messages,
                           Double temperature,
                           Integer maxTokens,
                           Consumer<String> onChunk,
                           Runnable onComplete,
                           Consumer<Throwable> onError) {
        ChatCompletionRequest req = new ChatCompletionRequest();
        req.setModel(model);
        req.setMessages(messages);
        req.setTemperature(temperature != null ? temperature : 0.3);
        req.setMax_tokens(maxTokens != null ? maxTokens : 1024);
        req.setStream(true);
        Flux<String> flux = llmWebClient.post()
                .uri("/chat/completions")
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.TEXT_EVENT_STREAM)
                .bodyValue(req)
                .retrieve()
                .bodyToFlux(String.class)
                .doOnError(ex -> log.error("调用 LLM 流式失败", ex));
        flux.subscribe(payload -> {
            String s = payload == null ? null : payload.trim();
            if (s == null || s.isEmpty()) return;
            if (s.startsWith("data:")) s = s.substring(5).trim();
            if ("[DONE]".equals(s)) return;
            try {
                JSONObject obj = JSON.parseObject(s);
                JSONArray choices = obj.getJSONArray("choices");
                if (choices != null && !choices.isEmpty()) {
                    JSONObject c0 = choices.getJSONObject(0);
                    JSONObject delta = c0.getJSONObject("delta");
                    if (delta != null) {
                        String content = delta.getString("content");
                        if (content != null) onChunk.accept(content);
                    }
                }
            } catch (Exception ignore) {}
        }, err -> {
            if (onError != null) onError.accept(err);
        }, () -> {
            if (onComplete != null) onComplete.run();
        });
    }
}
src/main/java/com/zy/ai/service/WcsDiagnosisService.java
@@ -3,6 +3,7 @@
import com.alibaba.fastjson.JSON;
import com.zy.ai.entity.ChatCompletionRequest;
import com.zy.ai.entity.WcsDiagnosisRequest;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -52,6 +53,44 @@
        // 调用大模型
        return llmChatService.chat(messages, 0.2, 2048);
    }
    public void diagnoseStream(WcsDiagnosisRequest request, SseEmitter emitter) {
        List<ChatCompletionRequest.Message> messages = new ArrayList<>();
        ChatCompletionRequest.Message system = new ChatCompletionRequest.Message();
        system.setRole("system");
        system.setContent(
                "你是一名资深 WCS(仓储控制系统)与自动化立库专家,熟悉:堆垛机、输送线、提升机、穿梭车等设备的任务分配和运行逻辑,也熟悉常见的系统卡死、任务不执行、设备空闲但无任务等问题模式。\n\n" +
                        "你将收到以下几类数据:\n" +
                        "1)任务信息(tasks):当前待执行/在执行/挂起任务\n" +
                        "2)设备实时数据(deviceRealtimeData):每台设备当前状态、是否在线、当前任务号等\n" +
                        "3)设备配置信息(deviceConfigs):设备是否启用、服务区域、允许的任务类型等\n" +
                        "4)系统日志(logs):按时间顺序的日志文本\n" +
                        "5)额外上下文(extraContext):如仓库代码、WCS 版本等\n\n" +
                        "你的目标是:帮助现场运维人员分析,为什么系统当前不执行任务,或者任务执行效率异常,指出可能是哪些设备导致的问题。\n\n" +
                        "请按以下结构输出诊断结果(使用简体中文):\n" +
                        "1. 问题概述(1-3 句话,概括当前系统状态)\n" +
                        "2. 可疑设备列表(列出 1-N 个设备编号,并说明每个设备为什么可疑,例如:配置禁用/长时间空闲/状态异常/任务分配不到它等)\n" +
                        "3. 可能原因(从任务分配、设备状态、配置错误、接口/通信异常等角度,列出 3-7 条)\n" +
                        "4. 建议排查步骤(步骤 1、2、3...,每步要尽量具体、可操作,例如:在某页面查看某字段、检查某个开关、对比某个状态位等)\n" +
                        "5. 风险评估(说明当前问题对业务影响程度:高/中/低,以及是否需要立即人工干预)\n" +
                        "6. WCS 逻辑优化建议(如果从日志/数据看出可能的系统逻辑缺陷,请给出简要建议,例如增加某个防呆校验、告警、监控等)\n"
        );
        messages.add(system);
        ChatCompletionRequest.Message user = new ChatCompletionRequest.Message();
        user.setRole("user");
        user.setContent(buildUserContent(request));
        messages.add(user);
        llmChatService.chatStream(messages, 0.2, 2048, s -> {
            try { emitter.send(SseEmitter.event().data(s)); } catch (Exception ignore) {}
        }, () -> {
            try { emitter.complete(); } catch (Exception ignore) {}
        }, e -> {
            try { emitter.completeWithError(e); } catch (Exception ignore) {}
        });
    }
    private String buildUserContent(WcsDiagnosisRequest request) {
@@ -114,4 +153,4 @@
        return sb.toString();
    }
}
}
src/main/webapp/static/js/ai/diagnose.js
New file
@@ -0,0 +1,43 @@
var sse;
function startDiagnosis() {
    if (sse) {
        sse.close();
    }
    $('#ai-output').text('');
    $('#start-btn').attr('disabled', true);
    $('#stop-btn').attr('disabled', false);
    var url = baseUrl + '/ai/diagnose/runAiStream';
    sse = new EventSource(url);
    sse.onmessage = function (e) {
        var curr = $('#ai-output').text();
        $('#ai-output').text(curr + e.data);
    };
    sse.onerror = function () {
        $('#start-btn').attr('disabled', false);
        $('#stop-btn').attr('disabled', true);
        if (sse) {
            sse.close();
        }
        layer.msg('连接已关闭或发生错误');
    };
}
function stopDiagnosis() {
    if (sse) {
        sse.close();
        sse = null;
    }
    $('#start-btn').attr('disabled', false);
    $('#stop-btn').attr('disabled', true);
}
$(function () {
    $('#stop-btn').attr('disabled', true);
    $('#start-btn').on('click', startDiagnosis);
    $('#stop-btn').on('click', stopDiagnosis);
    $('#clear-btn').on('click', function () { $('#ai-output').text(''); });
});
src/main/webapp/views/ai/diagnose.html
New file
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <title>AI诊断</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="../../static/layui/css/layui.css" media="all">
    <link rel="stylesheet" href="../../static/css/admin.css" media="all">
    <link rel="stylesheet" href="../../static/css/cool.css" media="all">
</head>
<body>
<div class="layui-fluid">
    <div class="layui-row">
        <div class="layui-col-md12">
            <div class="layui-card">
                <div class="layui-card-header">AI诊断</div>
                <div class="layui-card-body">
                    <div class="layui-form toolbar" id="ai-toolbar">
                        <button id="start-btn" type="button" class="layui-btn layui-btn-normal">开始诊断</button>
                        <button id="stop-btn" type="button" class="layui-btn layui-btn-danger">停止</button>
                        <button id="clear-btn" type="button" class="layui-btn">清空</button>
                    </div>
                    <hr class="layui-bg-gray">
                    <div id="ai-output" style="white-space: pre-wrap; font-family: Menlo, Monaco, Consolas, monospace; min-height: 240px; padding: 12px; border: 1px solid #e6e6e6; border-radius: 4px;"></div>
                </div>
            </div>
        </div>
    </div>
    <div class="layui-row">
        <div class="layui-col-md12">
            <div class="layui-card">
                <div class="layui-card-header">说明</div>
                <div class="layui-card-body">
                    <p>点击“开始诊断”后,前端将通过 SSE 与后端建立连接,并逐字显示 AI 的分析结果。</p>
                </div>
            </div>
        </div>
    </div>
</div>
<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../../static/layui/layui.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/ai/diagnose.js" charset="utf-8"></script>
</body>
</html>