package org.jk.rag.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.collections4.CollectionUtils;
import org.jk.common.mybatis.core.page.PageQuery;
import org.jk.common.sse.core.SseEmitterManager;
import org.jk.common.sse.dto.SseMessageDto;
import org.jk.rag.domain.JkSysOss;
import org.jk.rag.domain.bo.DialogLogBo;
import org.jk.rag.domain.dto.*;
import org.jk.rag.domain.entity.*;
import org.jk.rag.domain.vo.DialogLogVo;
import org.jk.rag.domain.vo.KnowledgeSliceVo;
import org.jk.rag.mapper.*;
import org.jk.rag.service.IDialogLogService;
import org.jk.rag.service.IJkRagService;
import org.jk.rag.utils.*;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import okhttp3.sse.EventSources;
import org.apache.commons.collections4.MapUtils;
import org.jk.common.core.config.DeepseekConfig;
import org.jk.common.core.constant.UserConstants;
import org.jk.common.core.domain.model.LoginUser;
import org.jk.common.core.enums.AppTypeEnum;
import org.jk.common.core.utils.IdUtils;
import org.jk.common.core.utils.StringUtils;
import org.jk.common.redis.utils.RedisUtils;
import org.jk.common.satoken.utils.LoginHelper;
import org.jk.system.domain.*;
import org.jk.system.domain.vo.SysDictDataVo;
import org.jk.system.domain.vo.SysOssVo;
import org.jk.system.mapper.*;
import org.jk.system.service.ISysOssService;
import org.jk.system.service.ISysUserService;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 高井 业务层处理
 *
 * @author jk
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class JkRagServiceImpl implements IJkRagService {
    private static final Logger logger = LoggerFactory.getLogger(JkRagServiceImpl.class);

    private final JkDialogMapper jkDialogMapper;


    private final JkApplicationMapper jkApplicationMapper;


    private final JkQuestionMapper jkQuestionMapper;


    private final JkKnowledgeMapper jkKnowledgeMapper;


    private final JkDocumentMapper jkDocumentMapper;


    private final ISysOssService sysOssService;


    private final JkSysOssMapper jkSysOssMapper;


    //private  MinioUtil minioUtil;


    private final JkDocumentSettingsMapper jkDocumentSettingsMapper;


    private final DeepseekConfig deepseekConfig;


    private final JkAppInfoMapper jkAppInfoMapper;


    private final JkMediaReplacementMapper jkMediaReplacementMapper;


    private final JkSliceInfoMapper jkSliceInfoMapper;


    private final DocumentAuditConfigMapper documentAuditConfigMapper;


    private final DocumentAuditInfoMapper documentAuditInfoMapper;



    private final TypeAppMapper appTypeMapper;


    private final JkAppCollectMapper appCollectMapper;


    private final SysDictDataMapper sysDictDataMapper;


    private final SysProjectAppMapper sysProjectAppMapper;


    private final JkFileInfoMapper fileInfoMapper;

    private final RedisSerialRateLimiter dynamicRateLimiter; // 注入分布式限流器

    private final SseEmitterManager sseEmitterManager;

    private final IDialogLogService dialogLogService;

    public static final String START_SIGN = "【";
    public static final String END_SIGN = "】";
    public static final String SYMBOL = "【示意图序号";

    public static final String USER_STR = "user";

    public static final String TODAY = "今日";

    public static final String BEFOR = "日前";

    private static final String TYAPPID = "1";

    private final SysProjectMapper sysProjectMapper;

    private final ISysUserService userService;

    private final RedissonClient redissonClient;

    private final Base64DecodeUtil base64DecodeUtil;

    @Override
    public void initDeepseekInfo() {
        //获取应用列表
//        List<JkApplication> appList = jkApplicationMapper.selectApplicationList(null);
        setRedisCache(null);
    }

    private void setRedisCache(String knowledgeId) {
        //获取知识库列表
        List<JkKnowledge> knowledgeList = null;
        if (StringUtils.isEmpty(knowledgeId)) {
            knowledgeList = jkKnowledgeMapper.selectAllKnowledgeList(null);
        } else {
            knowledgeList = jkKnowledgeMapper.selectAllKnowledgeList(JkKnowledge.builder().knowledgeId(knowledgeId).build());
        }

        //获取知识列表
        if (!knowledgeList.isEmpty()) {
            for (JkKnowledge bmKnowledge : knowledgeList) {
                JkDocument conditon = JkDocument.builder().knowledgeId(bmKnowledge.getKnowledgeId()).build();
                List<JkDocument> documentList = jkDocumentMapper.selectDocumentList(conditon);
                if (!documentList.isEmpty()) {
                    for (JkDocument bmDocument : documentList) {
                        //获取知识图片列表
                        JkMediaReplacement conditonR = JkMediaReplacement.builder().documentId(bmDocument.getDocumentId()).build();
                        List<JkMediaReplacement> imageList = jkMediaReplacementMapper.selectMediaList(conditonR);
                        if (!imageList.isEmpty()) {
                            for (JkMediaReplacement image : imageList) {
                                //redissonClient.getMapCache(image.getOriginText(), image.getMediaUrl(), Duration.ofMillis(1000));
                            }
                        }
                    }
                }
            }
        }
    }

    public SseEmitter sseInvoke(JkSseInfoParams sseParams) {
        // 1. 记录开始时间（毫秒级）
        long startTime = System.currentTimeMillis();
        JkApplication appInfo = jkApplicationMapper.selectTargetApplication(JkApplication.builder().appId(sseParams.getAppId()).build());
        DialogLogBo dialogLogBo = new DialogLogBo();
        dialogLogBo.setAppId(sseParams.getAppId());
        if (appInfo != null) {
            List<String> idList = Arrays.stream(appInfo.getKnowledgeIds().split(",")) // 按「逗号+空格」拆分字符串为数组
                .filter(str -> str != null && !str.trim().isEmpty()) // 过滤 null 和空字符串（防异常）
                .collect(Collectors.toList());
            JkKnowledge knowledgeParam = JkKnowledge.builder().knowledgeIds(idList).build();
            List<JkKnowledge> knowledgeList = jkKnowledgeMapper.selectAllKnowledgeList(knowledgeParam);
            if (knowledgeList != null && !knowledgeList.isEmpty()) {
                SseEmitter sseEmitter = new SseEmitter(0L);
                String url = deepseekConfig.getBaseurl() + deepseekConfig.getChat();
                JkAppInfo info = jkAppInfoMapper.selectAppInfoByAppId(sseParams.getAppId());
                JSONObject json = JSONObject.parseObject(info.getAppInfo());
                //重新设置knowledgeIds为数组
                json.put("knowledgeIds",idList);
                decodeMessage(sseParams.getMessages());
                decodeMessage(sseParams.getPrompt());
                //String query = null;
                List<JkPromptInfo> messages = new ArrayList<>();
                JSONObject knowledgeInfo = JSONObject.parseObject(json.getString("knowledgeInfo"));
                String isMultiRound = knowledgeInfo.getString("is_multi_round");
                if(knowledgeInfo.containsKey("is_multi_round")) {
                    if(isMultiRound.equals("Y")) {
                        Integer multiRound = Integer.valueOf(knowledgeInfo.getString("multi_round"));
                        if (sseParams.getPrompt() != null && !sseParams.getPrompt().isEmpty()) {
                            int listSize = sseParams.getPrompt().size();
                            if (listSize >= multiRound) {
                                // 取最后5条：subList(起始索引, 结束索引)，结束索引是开区间（不包含）
                                // 起始索引 = 总长度 - 5，结束索引 = 总长度
                                List<JkPromptInfo> lastFive = sseParams.getPrompt().subList(listSize - multiRound, listSize);
                                // 注意：subList返回的是原列表的「视图」，需new ArrayList包装，避免后续原列表修改影响messages
                                messages = new ArrayList<>(lastFive);
                            } else {
                                // 小于5条：赋值所有数据（同样包装，避免视图关联）
                                messages = new ArrayList<>(sseParams.getPrompt());
                            }
                        } else if (sseParams.getMessages() != null && !sseParams.getMessages().isEmpty()) {
                            int listSize = sseParams.getMessages().size();
                            if (listSize >= multiRound) {
                                // 取最后5条：subList(起始索引, 结束索引)，结束索引是开区间（不包含）
                                // 起始索引 = 总长度 - 5，结束索引 = 总长度
                                List<JkPromptInfo> lastFive = sseParams.getMessages().subList(listSize - multiRound, listSize);
                                // 注意：subList返回的是原列表的「视图」，需new ArrayList包装，避免后续原列表修改影响messages
                                messages = new ArrayList<>(lastFive);
                            } else {
                                // 小于5条：赋值所有数据（同样包装，避免视图关联）
                                messages = new ArrayList<>(sseParams.getMessages());
                            }
                        }
                    } else {
                        if(sseParams.getMessages() != null && !sseParams.getMessages().isEmpty() ) {
                            messages.add(sseParams.getMessages().get(sseParams.getMessages().size() - 1));
                        } else {
                            messages.add(sseParams.getPrompt().get(sseParams.getPrompt().size() - 1));
                        }
                    }
                } else {
                    if(sseParams.getMessages() != null && !sseParams.getMessages().isEmpty() ) {
                        messages.add(sseParams.getMessages().get(sseParams.getMessages().size() - 1));
                    } else {
                        messages.add(sseParams.getPrompt().get(sseParams.getPrompt().size() - 1));
                    }
                }

//                if(sseParams.getMessages() != null && !sseParams.getMessages().isEmpty() ) {
//                    query = sseParams.getMessages().get(sseParams.getMessages().size() - 1).getContent();
//                } else {
//                    query = sseParams.getPrompt().get(sseParams.getPrompt().size() - 1).getContent();
//                }
                log.info("deepseek聊天请求参数：" + messages);
                json.put("messages", messages);
                json.put("embeddingId", knowledgeList.get(0).getEmbeddingId());
                if(UserConstants.YES.equals(json.getString("isDeepThink"))) {
                    json.put("enable_think", true);
                } else {
                    json.put("enable_think", false);
                }

                RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8"), json.toJSONString());
                Request request = buildPostRequest(url, requestBody);
                // 使用EventSourceListener处理来自服务器的SSE事件
                EventSourceListener listener = new EventSourceListener() {
                    private String preData = "";
                    //图片识别
                    private String symbolData = "";

                    @Override
                    public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) {
                        log.info("deepseek Connection opened. Headers: {}", response.headers());
                        log.info("deepseek Request URL: {}", response.request().url());
                    }

                    @Override
                    public void onClosed(@NotNull EventSource eventSource) {
                        log.info("deepseek Connection closed.");
                        sseEmitter.complete();
                    }

                    @Override
                    public void onEvent(@NotNull EventSource eventSource, String id, String type, @NotNull String data) {
                        if (!StringUtils.isEmpty(data) && data.length()>preData.length() && org.jk.common.core.utils.StringUtils.isNotEmpty(type) && "add".equals(type)) {
                            if(data.length()<preData.length()) {
                                logger.info(data);
                            }
                            String newData = data.substring(preData.length());
                            logger.info("deepseek聊天返回消息：" + data);
                            preData = data;

                            if (newData.indexOf(START_SIGN) > -1 || symbolData.length() > 0) {
                                symbolData = symbolData + newData;
                                //接收到了完整的图片标识
                                int endSignPos = symbolData.indexOf(END_SIGN);
                                if (endSignPos > -1) {
                                    JSONObject json = new JSONObject();
                                    json.put("id", id);
                                    json.put("event", "add");
                                    //是图片
                                    if (symbolData.indexOf(SYMBOL) > -1) {
                                        //获取完整Key
                                        int startPos = symbolData.indexOf(START_SIGN);
                                        int endPos = symbolData.indexOf(END_SIGN) + 1;
                                        String key = symbolData.substring(startPos, endPos);
                                        List<String> url = jkMediaReplacementMapper.selectMediaByText(key);
                                        //String url = RedisUtils.getCacheObject(key);
                                        if (!CollectionUtils.isEmpty(url)) {
//                                            if(url.get(0).contains("https://10.1.28.14:9000")){
//                                                url[0] = url.get(0).replace("https://10.1.28.14:9000", "https://minio.jkec.info:9000");
//                                            }
                                            //匹配markdown图片格式
//                                            symbolData = symbolData.replace(key, "![](" + url + ")");
                                        } else {
                                            logger.error("图片标识符"+key+"没有获取到对应的URL{}: " + symbolData);
                                        }
                                        //检查是否当前token包含其他图片的开始符号”【“
                                        int lastPos = symbolData.lastIndexOf(START_SIGN);
                                        if(lastPos > endSignPos){
                                            // 从'【' 开始截取
                                            newData = symbolData.substring(0, lastPos);
                                            symbolData = symbolData.substring(lastPos);
                                            logger.info("截取结果{}: " + symbolData);
                                        } else {
                                            newData = symbolData;
                                            symbolData = "";
                                        }
                                        if(!CollectionUtils.isEmpty(url)) {
                                            newData = newData.replace(key, " ![](" + url.get(0) + ")");
                                        } else {
                                            newData = newData.replace(key, " ![]()");
                                        }

                                    } else {
                                        newData = symbolData;
                                        symbolData = "";
                                    }
                                    json.put("data", newData);

                                    log.info("获取到图片替换结束符号，返回前端信息：" + json);
                                    send(sseEmitter, json);
                                    //如果超过长度200还没收到】符号，直接返回。
                                } else if (symbolData.length() > 200) {
                                    JSONObject json = new JSONObject();
                                    json.put("id", id);
                                    json.put("event", "add");
                                    json.put("data", symbolData);
                                }
                            } else {
                                JSONObject json = new JSONObject();
                                json.put("id", id);
                                json.put("event", type);
                                json.put("data", newData);
                                log.info("deepseek返回信息：" + json);
                                send(sseEmitter, json);
                            }
                        }
                        if (org.jk.common.core.utils.StringUtils.isNotEmpty(type) && "finish".equals(type)) {
                            log.info("deepseek聊天结束");
                            JSONObject json = new JSONObject();
                            json.put("id", id);
                            json.put("event", type);
                            json.put("data", data);
                            log.info("deepseek返回信息：" + json);
                            // 2. 记录结束时间，计算耗时（finally保证即使方法抛异常也会执行）
                            long endTime = System.currentTimeMillis();
                            long costTime = endTime - startTime;
                            dialogLogBo.setCostTime(costTime);
                            dialogLogBo.setStatus("0");
                            dialogLogService.insertByBo(dialogLogBo);
                            send(sseEmitter, json);
                        }
                    }

                    @Override
                    public void onFailure(@NotNull EventSource eventSource, Throwable t, Response response) {
                        dialogLogBo.setStatus("1");
                        dialogLogService.insertByBo(dialogLogBo);
                        if (response != null) {
                            log.error("deepseek Connection failed. Response code: {}, message: {}, body: {}",
                                response.code(), response.message(), response.body() != null ? response.body().toString() : "null");
                        } else {
                            log.error("deepseek Connection failed with no response", t);
                        }
                        sseEmitter.completeWithError(t);
                    }

                    private void send(SseEmitter sseEmitter, Object obj) {
                        try {
                            sseEmitter.send(obj);
                        } catch (IOException e) {
                            log.error("deepseek 推送数据失败", e);
                        }
                    }
                };

                OkHttpClient client = buildOkHttpClient();
                EventSource.Factory factory = EventSources.createFactory(client);
                final EventSource eventSources = factory.newEventSource(request, listener);

                // 客户端主动关闭连接
                sseEmitter.onCompletion(() -> {
                    dialogLogBo.setStatus("1");
                    dialogLogService.insertByBo(dialogLogBo);
                    logger.info("deepseek客户端主动关闭连接 -- SSE 连接关闭");
                });

                // 超时回调
                sseEmitter.onTimeout(() -> {
                    dialogLogBo.setStatus("1");
                    dialogLogService.insertByBo(dialogLogBo);
                    logger.info("deepseek客户端连接超时 -- SSE 连接关闭");
                    if(eventSources != null) {
                        logger.info("deepseek超时回调 -- 成功关闭SSE连接 ");
                        eventSources.cancel();
                    }
                });

                // 错误回调
                sseEmitter.onError(e -> {
                    dialogLogBo.setStatus("1");
                    dialogLogService.insertByBo(dialogLogBo);
                    logger.info("deepseek客户端回调失败 -- SSE 连接关闭");
                    if(eventSources != null) {
                        logger.info("deepseek错误回调 -- 成功关闭SSE连接 ");
                        eventSources.cancel();
                    }
                });
                return sseEmitter;
            }
        }
        dialogLogBo.setStatus("1");
        dialogLogService.insertByBo(dialogLogBo);
        return null;
    }

    public SseEmitter previewSseInvoke(JkPreviewSeeParams params) {
        JkApplicationParams appInfo = params.getJkApplicationParams();
        if (appInfo != null) {
            List<String> idList = appInfo.getKnowledge_ids();
            JkKnowledge knowledgeParam = JkKnowledge.builder().knowledgeIds(idList).build();
            List<JkKnowledge> knowledgeList = jkKnowledgeMapper.selectAllKnowledgeList(knowledgeParam);
            if (knowledgeList != null && !knowledgeList.isEmpty()) {
                SseEmitter sseEmitter = new SseEmitter(0L);
                String url = deepseekConfig.getBaseurl() + deepseekConfig.getChat();
                //JkAppInfo info = jkAppInfoMapper.selectAppInfoByAppId(sseParams.getAppId());
                JkApplication info = JkApplication.builder()
                    .name(params.getJkApplicationParams().getName())
                    .desc(params.getJkApplicationParams().getDesc())
                    .model(params.getJkApplicationParams().getModel())
                    .knowledgeIds(params.getJkApplicationParams().getKnowledge_ids().stream().collect(Collectors.joining(",")))
                    .topP(params.getJkApplicationParams().getTopP())
                    .topK(params.getJkApplicationParams().getTopK())
                    .temperature(params.getJkApplicationParams().getTemperature())
                    .maxToken(params.getJkApplicationParams().getMax_token())
                    .prompt(base64DecodeUtil.decodeIfNeeded(params.getJkApplicationParams().getPrompt()))
                    .groupVisible(params.getJkApplicationParams().getGroupVisible())
                    .knowledgeInfo(params.getJkApplicationParams().getKnowledge_info())
                    .sliceCount(params.getJkApplicationParams().getSlice_count())
                    .typeId(params.getJkApplicationParams().getTypeId())
                    .visible(params.getJkApplicationParams().getVisible())
                    .iconColor(params.getJkApplicationParams().getIconColor())
                    .iconType(params.getJkApplicationParams().getIconType())
                    .isDeepThink(params.getJkApplicationParams().getIsDeepThink())
                    .sort(params.getJkApplicationParams().getSort())
                    .build();
                JSONObject json = JSONObject.from(info);
                //重新设置knowledgeIds为数组
                json.put("knowledgeIds",idList);
                decodeMessage(params.getMessages());
                //String query = null;
                List<JkPromptInfo> messages = new ArrayList<>();
                JSONObject knowledgeInfo = JSONObject.parseObject(json.getString("knowledgeInfo"));
                String isMultiRound = knowledgeInfo.getString("is_multi_round");
                if(knowledgeInfo.containsKey("is_multi_round")) {
                    if(isMultiRound.equals("Y")) {
                        Integer multiRound = Integer.valueOf(knowledgeInfo.getString("multi_round"));
                        if (params.getMessages() != null && !params.getMessages().isEmpty()) {
                            int listSize = params.getMessages().size();
                            if (listSize >= multiRound) {
                                // 取最后5条：subList(起始索引, 结束索引)，结束索引是开区间（不包含）
                                // 起始索引 = 总长度 - 5，结束索引 = 总长度
                                List<JkPromptInfo> lastFive = params.getMessages().subList(listSize - multiRound, listSize);
                                // 注意：subList返回的是原列表的「视图」，需new ArrayList包装，避免后续原列表修改影响messages
                                messages = new ArrayList<>(lastFive);
                            } else {
                                // 小于5条：赋值所有数据（同样包装，避免视图关联）
                                messages = new ArrayList<>(params.getMessages());
                            }
                        }
                    } else {
                        if(params.getMessages() != null && !params.getMessages().isEmpty() ) {
                            messages.add(params.getMessages().get(params.getMessages().size() - 1));
                        }
                    }
                } else {
                    if(params.getMessages() != null && !params.getMessages().isEmpty() ) {
                        messages.add(params.getMessages().get(params.getMessages().size() - 1));
                    }
                }

                log.info("deepseek聊天请求参数：" + messages);
                json.put("messages", messages);
                json.put("embeddingId", knowledgeList.get(0).getEmbeddingId());
                if(UserConstants.YES.equals(json.getString("isDeepThink"))) {
                    json.put("enable_think", true);
                } else {
                    json.put("enable_think", false);
                }

                RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8"), json.toJSONString());
                Request request = buildPostRequest(url, requestBody);
                // 使用EventSourceListener处理来自服务器的SSE事件
                EventSourceListener listener = new EventSourceListener() {
                    private String preData = "";
                    //图片识别
                    private String symbolData = "";

                    @Override
                    public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) {
                        log.info("deepseek Connection opened. Headers: {}", response.headers());
                        log.info("deepseek Request URL: {}", response.request().url());
                    }

                    @Override
                    public void onClosed(@NotNull EventSource eventSource) {
                        log.info("deepseek Connection closed.");
                        sseEmitter.complete();
                    }

                    @Override
                    public void onEvent(@NotNull EventSource eventSource, String id, String type, @NotNull String data) {
                        if (!StringUtils.isEmpty(data) && data.length()>preData.length() && org.jk.common.core.utils.StringUtils.isNotEmpty(type) && "add".equals(type)) {
                            if(data.length()<preData.length()) {
                                logger.info(data);
                            }
                            String newData = data.substring(preData.length());
                            logger.info("deepseek聊天返回消息：" + data);
                            preData = data;

                            if (newData.indexOf(START_SIGN) > -1 || symbolData.length() > 0) {
                                symbolData = symbolData + newData;
                                //接收到了完整的图片标识
                                int endSignPos = symbolData.indexOf(END_SIGN);
                                if (endSignPos > -1) {
                                    JSONObject json = new JSONObject();
                                    json.put("id", id);
                                    json.put("event", "add");
                                    //是图片
                                    if (symbolData.indexOf(SYMBOL) > -1) {
                                        //获取完整Key
                                        int startPos = symbolData.indexOf(START_SIGN);
                                        int endPos = symbolData.indexOf(END_SIGN) + 1;
                                        String key = symbolData.substring(startPos, endPos);
                                        List<String> url = jkMediaReplacementMapper.selectMediaByText(key);
                                        //String url = RedisUtils.getCacheObject(key);
                                        if (!CollectionUtils.isEmpty(url)) {
//                                            if(url.get(0).contains("https://10.1.28.14:9000")){
//                                                url[0] = url.get(0).replace("https://10.1.28.14:9000", "https://minio.jkec.info:9000");
//                                            }
                                            //匹配markdown图片格式
//                                            symbolData = symbolData.replace(key, "![](" + url + ")");
                                        } else {
                                            logger.error("图片标识符"+key+"没有获取到对应的URL{}: " + symbolData);
                                        }
                                        //检查是否当前token包含其他图片的开始符号”【“
                                        int lastPos = symbolData.lastIndexOf(START_SIGN);
                                        if(lastPos > endSignPos){
                                            // 从'【' 开始截取
                                            newData = symbolData.substring(0, lastPos);
                                            symbolData = symbolData.substring(lastPos);
                                            logger.info("截取结果{}: " + symbolData);
                                        } else {
                                            newData = symbolData;
                                            symbolData = "";
                                        }
                                        if(!CollectionUtils.isEmpty(url)) {
                                            newData = newData.replace(key, " ![](" + url.get(0) + ")");
                                        } else {
                                            newData = newData.replace(key, " ![]()");
                                        }

                                    } else {
                                        newData = symbolData;
                                        symbolData = "";
                                    }
                                    json.put("data", newData);

                                    log.info("获取到图片替换结束符号，返回前端信息：" + json);
                                    send(sseEmitter, json);
                                    //如果超过长度200还没收到】符号，直接返回。
                                } else if (symbolData.length() > 200) {
                                    JSONObject json = new JSONObject();
                                    json.put("id", id);
                                    json.put("event", "add");
                                    json.put("data", symbolData);
                                }
                            } else {
                                JSONObject json = new JSONObject();
                                json.put("id", id);
                                json.put("event", type);
                                json.put("data", newData);
                                log.info("deepseek返回信息：" + json);
                                send(sseEmitter, json);
                            }
                        }
                        if (org.jk.common.core.utils.StringUtils.isNotEmpty(type) && "finish".equals(type)) {
                            log.info("deepseek聊天结束");
                            JSONObject json = new JSONObject();
                            json.put("id", id);
                            json.put("event", type);
                            json.put("data", data);
                            log.info("deepseek返回信息：" + json);
                            send(sseEmitter, json);
                        }
                    }

                    @Override
                    public void onFailure(@NotNull EventSource eventSource, Throwable t, Response response) {
                        if (response != null) {
                            log.error("deepseek Connection failed. Response code: {}, message: {}, body: {}",
                                response.code(), response.message(), response.body() != null ? response.body().toString() : "null");
                        } else {
                            log.error("deepseek Connection failed with no response", t);
                        }
                        sseEmitter.completeWithError(t);
                    }

                    private void send(SseEmitter sseEmitter, Object obj) {
                        try {
                            sseEmitter.send(obj);
                        } catch (IOException e) {
                            log.error("deepseek 推送数据失败", e);
                        }
                    }
                };

                OkHttpClient client = buildOkHttpClient();
                EventSource.Factory factory = EventSources.createFactory(client);
                final EventSource eventSources = factory.newEventSource(request, listener);

                // 客户端主动关闭连接
                sseEmitter.onCompletion(() -> {
                    logger.info("deepseek客户端主动关闭连接 -- SSE 连接关闭");
                });

                // 超时回调
                sseEmitter.onTimeout(() -> {
                    logger.info("deepseek客户端连接超时 -- SSE 连接关闭");
                    if(eventSources != null) {
                        logger.info("deepseek超时回调 -- 成功关闭SSE连接 ");
                        eventSources.cancel();
                    }
                });

                // 错误回调
                sseEmitter.onError(e -> {
                    logger.info("deepseek客户端回调失败 -- SSE 连接关闭");
                    if(eventSources != null) {
                        logger.info("deepseek错误回调 -- 成功关闭SSE连接 ");
                        eventSources.cancel();
                    }
                });
                return sseEmitter;
            }
        }
        return null;
    }

    @Override
    public List<String> asyncCompletions(JkCompletionsParams params) {
        List<String> list = new ArrayList<>();
        JkApplication vo = jkApplicationMapper.selectTargetApplication(JkApplication.builder().appId(params.getAppId()).build());
        if (vo != null) {
            JkPromptInfo info = new JkPromptInfo();
            info.setContent(deepseekConfig.getPrompt());
            info.setRole("user");
            decodeMessage(params.getMessages());
            params.getMessages().add(info);

            JSONObject jsonObject = new JSONObject();

            jsonObject.put("model", vo.getModel());
            jsonObject.put("messages", params.getMessages());

            jsonObject.put("chat_id", params.getChat_id());
            JSONObject result = getasyncCompletions(jsonObject);
            if (result != null && result.getInteger("code") == 200) {
                JSONArray array = result.getJSONArray("data");
                list.addAll(array.toJavaList(String.class));
            }
        }

        return list;
    }

    private JSONObject getasyncCompletions(JSONObject jsonObject) {
        String url = deepseekConfig.getBaseurl() + deepseekConfig.getAsyncCompletions();
        RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8"), jsonObject.toJSONString());
        Request request = buildPostRequest(url, requestBody);
        OkHttpClient client = buildOkHttpClient();
        try {
            Response response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                String body = response.body().string();
                if (isJsonObject(body)) {
                    return JSON.parseObject(body);
                }
            }
        } catch (IOException e) {
            log.info("deepseek Request URL: {}", e.getMessage());
        }
        return null;
    }

    @Override
    public List<JkDialogRespDTO> getDialogDetail(String dialogId) {
        List<JkDialogRespDTO> detail = jkDialogMapper.selectDialogDetail(dialogId);
        if(detail != null && detail.size() != 0){
            for(JkDialogRespDTO dto : detail){
                if(StringUtils.isNotEmpty(dto.getSliceJson())){
                    dto.setSliceInfo(JSON.parseObject(dto.getSliceJson(), JkSliceInfoDTO.class));
                }
                if("user".equals(dto.getType())){
                    List<JkFileInfo> fileInfoList = fileInfoMapper.getFileInfoById(null, dto.getDialog_id(), dto.getDid());

                    if(fileInfoList != null && !fileInfoList.isEmpty()){
                        List<JkDocumentDTO>  documentDTOS = new ArrayList<>();
                        for(JkFileInfo fileInfo : fileInfoList){
                            JkDocumentDTO documentDTO = new JkDocumentDTO();
                            BeanUtils.copyProperties(fileInfo, documentDTO);
                            documentDTOS.add(documentDTO);
                        }
                        dto.setDocuments(documentDTOS);
                    }
                }
            }
        }
        return detail;
    }

    @Override
    public void saveDialog(JkDialogReqDTO dialogReqDTO) {
        String dialogId = null;
        JkDialogRespDTO dialogVo = null;
        if(null == dialogReqDTO) {
            dialogReqDTO.setAppId(TYAPPID);
        }
        if (org.jk.common.core.utils.StringUtils.isNotEmpty(dialogReqDTO.getId())) {
            dialogVo = jkDialogMapper.selectDialogById(dialogReqDTO.getId());
        }

        if (dialogVo == null) {
            jkDialogMapper.insertDialog(dialogReqDTO);
            dialogId = dialogReqDTO.getId();
        } else {
            dialogId = dialogReqDTO.getId();
        }

        for (JkDialogDetailReqDTO dto : dialogReqDTO.getMessages()) {
            dto.setDialogId(dialogId);
            dto.setContent(base64DecodeUtil.decodeIfNeeded(dto.getContent()));
            JkDialogRespDTO detail = jkDialogMapper.selectDialogDetailById(dto.getId());
            if (detail == null) {
                if (dto.getSliceInfo() != null) dto.setSliceJson(JSON.toJSONString(dto.getSliceInfo()));
                jkDialogMapper.insertDialogDetail(dto);
            }

            if(dto.getDocuments() != null && !dto.getDocuments().isEmpty()){
                for(JkDocumentDTO documentVo : dto.getDocuments()){
                    if(!"".equals(documentVo.getId())) {
                        List<JkFileInfo> fileInfo = fileInfoMapper.getFileInfoById(documentVo.getId(), null, null);
                        if((fileInfo == null || fileInfo.isEmpty()) && "user".equals(dto.getRole())) {
                            JkFileInfo params = new JkFileInfo();
                            params.setId(documentVo.getId());
                            params.setName(documentVo.getName());
                            params.setUrl(documentVo.getUrl());
                            params.setDialogId(dialogId);
                            params.setDialogDid(dto.getId());
                            fileInfoMapper.insertFileInfo(params);
                        }
                    }
                }

            }
        }
    }

    @Override
    public List<Object> getDialogList(String appId, String userId) {
        List<JkDialogRespDTO> dialogRespDTOS = jkDialogMapper.selectDialogList(appId, userId);
        //当前日期
        LocalDate today = LocalDate.now();
        //根据时间分组降序排序
        NavigableMap<LocalDateTime, List<JkDialogRespDTO>> groupList = dialogRespDTOS.stream().collect(Collectors.groupingBy(JkDialogRespDTO::getCreate_time, TreeMap::new, Collectors.toList())).descendingMap();
        List<Object> obj = new ArrayList();
        if (!groupList.isEmpty()) {
            for (Map.Entry<LocalDateTime, List<JkDialogRespDTO>> entry : groupList.entrySet()) {
                // 对比两个时间相差多少天
                long daysBetween = ChronoUnit.DAYS.between(today, entry.getKey());
                Map<String, Object> m = new HashMap();
                m.put("key", System.nanoTime());
                m.put("type", "group");
                if (daysBetween == 0l) {
                    m.put("label", TODAY);
                    m.put("children", setValue(entry.getValue(), appId));
                    obj.add(m);
                } else {
                    m.put("label", -daysBetween + BEFOR);
                    m.put("children", setValue(entry.getValue(), appId));
                    obj.add(m);
                }
            }
        }
        return obj;
    }

    @Override
    public void DelDialogDetail(String id) {
        int result = jkDialogMapper.delDialog(id);
        if (result > 0) {
            jkDialogMapper.delDialogDetail(id);
        }
    }

    @Override
    public void updateDialog(JkDialogReqDTO dialogReqDTO) {
        jkDialogMapper.updateDialog(dialogReqDTO);
    }

    @Override
    public List<JkDialogRespDTO> exportExcel(String dialogId) {
        return jkDialogMapper.selectDialogExport(dialogId);
    }

    @Override
    public int createKnowledge(JkKnowledgeParams knowledge) {
        List<JkKnowledge> list = jkKnowledgeMapper.selectAllKnowledgeList(JkKnowledge.builder().name(knowledge.getName()).build());
        if(!list.isEmpty()) {
            throw new RuntimeException("知识库名称已存在!");
        }
        SnowflakeDigitGenerator snowflakeDigitGenerator = new SnowflakeDigitGenerator(1, 1);
        long knowledgeId = snowflakeDigitGenerator.nextId();

        String url = deepseekConfig.getBaseurl() + deepseekConfig.getCreateKnowledge();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("knowledge_id", "a" + knowledgeId);
        jsonObject.put("embedding_id", knowledge.getEmbeddingId());

        RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8"), jsonObject.toJSONString());
        Request request = buildPostRequest(url, requestBody);
        OkHttpClient client = buildOkHttpClient();
        Long createdBy = LoginHelper.getLoginUser() == null ? null:LoginHelper.getLoginUser().getUserId();
        try {
            Response response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                String body = response.body().string();
                JSONObject obj = JSON.parseObject(body);
                Integer code = obj.getInteger("code");
                if (code == 200) {
                    JkKnowledge params = JkKnowledge.builder()
                            .knowledgeId("a" + knowledgeId)
                            .name(knowledge.getName())
                            .embeddingId(knowledge.getEmbeddingId())
                            .description(knowledge.getDescription())
                            .visible(knowledge.getVisible())
                            .knowledgeGraph(knowledge.getKnowledgeGraph())
                            .splittingType(knowledge.getSplittingType())
                            .build();
                    params.setCreateBy(Long.valueOf(createdBy));
                    return jkKnowledgeMapper.insertKnowledge(params);
                }
            } else {
                logger.error("创建知识库调用python接口失败,返回状态码：{}", response.code());
            }
        } catch (IOException e) {
            logger.error("创建知识库调用python接口失败", e.getMessage());
        }
        return 0;
    }

    @Override
    public int updateKnowledge(JkKnowledgeParams knowledge, String knowledgeId) {
        List<JkKnowledge> list = jkKnowledgeMapper.selectAllKnowledgeList(JkKnowledge.builder().name(knowledge.getName()).build());
        if(!list.isEmpty()) {
            if (!list.get(0).getKnowledgeId().equals(knowledgeId)) {
                throw new RuntimeException("知识库名称已存在!");
            }

        }
        JkKnowledge params = JkKnowledge.builder().knowledgeId(knowledgeId)
                .name(knowledge.getName())
                .description(knowledge.getDescription())
                .embeddingId(knowledge.getEmbeddingId())
                .visible(knowledge.getVisible())
                .knowledgeGraph(knowledge.getKnowledgeGraph())
                .splittingType(knowledge.getSplittingType())
                .build();
        Long updateBy = LoginHelper.getLoginUser() == null ? null:LoginHelper.getLoginUser().getUserId();
        params.setUpdateBy(Long.valueOf(updateBy));
        params.setUpdateTime(new Date());
        return jkKnowledgeMapper.updateKnowledge(params);
    }

    @Override
    public JkKnowledge detailKnowledge(String knowledgeId) {
        JkKnowledge vo = JkKnowledge.builder().knowledgeId(knowledgeId).build();
        return jkKnowledgeMapper.selectTargetKnowledge(vo);
    }

    @Override
    public int delKnowledge(String knowledgeId) {
        int appCount = jkApplicationMapper.selectAppByKnowledgeId(knowledgeId);
        if(appCount > 0) {
            throw new RuntimeException("知识库已被引用,不能删除！");
        }
        String url = deepseekConfig.getBaseurl() + deepseekConfig.getDeleteKnowledge() + "/" + knowledgeId;
        Request request = buildDeleteRequest(url);
        OkHttpClient client = buildOkHttpClient();
        Response response = null;
        try {
            response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                String body = response.body().string();
                JSONObject obj = JSON.parseObject(body);
                Integer code = obj.getInteger("code");
                if (code == 200) {
//                    JkKnowledge info = jkKnowledgeMapper.selectTargetKnowledge(JkKnowledge.builder().knowledgeId(knowledgeId).build());
                    List<JkMediaReplacement>  list = jkMediaReplacementMapper.selectMediaList(JkMediaReplacement.builder().knowledgeId(knowledgeId).build());
                    List<JkDocument> documentList = jkDocumentMapper.selectDocumentList(JkDocument.builder().knowledgeId(knowledgeId).build());
                    int knowledgeInt = jkKnowledgeMapper.delKnowledge(knowledgeId);
                    if (documentList.size() > 0) {
                        jkDocumentMapper.delDocumentByKnowledgeId(knowledgeId);
                        //jkDocumentSettingsMapper.deleteDocumentSettingsByKnowledgeId(knowledgeId);

                        if(list != null && list.size() > 0){

                            try {
                                for(JkDocument document: documentList){
                                    logger.info("删除知识库->删除minio文件");
                                    List<JkDocument> urlDocuments = jkDocumentMapper.selectDocumentList(JkDocument.builder().ossId(document.getOssId()).build());
                                    //如果没有多个文档引用则删除上传oss文件
                                    if (!(urlDocuments.size() > 1)) {
                                        logger.info("删除minio文件");
                                        sysOssService.deleteWithValidByIds(List.of(Long.valueOf(document.getOssId())), false);
                                    }
                                    List<JkMediaReplacement> mediaList = jkMediaReplacementMapper.selectMediaList(JkMediaReplacement.builder().documentId(document.getDocumentId()).build());
                                    // 删除图片
                                    jkMediaReplacementMapper.deleteMedia(document.getDocumentId());
                                    if (mediaList != null && mediaList.size() > 0) {
                                        //list.stream()
                                        for (int k = 1; k < mediaList.size(); k++) {
                                            logger.info("删除minio图片{}", k);
                                            if(null !=mediaList.get(k).getOssId()){
                                                sysOssService.deleteWithValidByIds(List.of(Long.valueOf(mediaList.get(k).getOssId())), false);
                                            }
                                        }
                                    }
                                    //minioUtil.remove("deepseek-doc", "/"+document.getName());
                                }

                                for (int k = 0; k < list.size(); k++ ) {
                                    logger.info("删除知识库->删除minio图片{}", k);
                                    String key = "/pdf/"+knowledgeId+"/"+list.get(k).getDocumentId()+"/【示意图序号_"+list.get(k).getDocumentId()+"_"+k+1+"】.jpg";
                                    logger.info("删除知识库->删除minio图片key{}", key);
                                    //minioUtil.remove("deepseek-doc" , key);
                                }
                            } catch (Exception e) {
                                logger.error("删除知识库->删除minion文件失败", e.getMessage());
                                throw new RuntimeException(e);
                            }

                        }
                    }
                    return knowledgeInt;
                }
            }
        } catch (IOException e) {
            logger.error("删除知识库调用python接口失败", e.getMessage());
        }
        return 0;
    }

    @Async
    @Override
    public CompletableFuture<Integer> uploadDocument(JkDocumentParams documentParams ,List<JkDocument> documentList,Boolean flg) {
        String tenantId = documentParams.getTenantId();
        for (JkDocument jkDocument : documentList) {
            String taskId = "task-" + System.currentTimeMillis() + "-" + UUID.randomUUID().toString().substring(0, 8);
            documentParams.setName(jkDocument.getName());
            documentParams.setDocumentId(jkDocument.getDocumentId());
            logger.info("=== 开始提交任务 === 任务ID: {}, 租户ID: {}, 文档: {}",
                taskId, tenantId, documentParams.getName());

            // ========== 关键修改1：提前查询oldDocument，在主线程判断oldFlg ==========
            boolean oldFlg = false; // 移到主线程定义（原在异步任务内）
            JkSliceDocument oldDocument = jkDocumentMapper.selectDocumentByOssId(
                jkDocument.getOssId(),
                jkDocument.getCustomSeparator(),
                jkDocument.getSliceValue(),
                jkDocument.getParsingType(),
                jkDocument.getStandardClassification()
            );
            if (null != oldDocument && oldDocument.getStatus().equals("0")) {
                oldFlg = true;
                logger.info("任务 {} 满足oldFlg=true条件，跳过本次循环", taskId);
                jkDocumentMapper.updateDocument(JkDocument.builder().documentId(jkDocument.getDocumentId()).status("2").errorMessage("重复文件上传错误").build());
                SseMessageDto sseMessageDto = new SseMessageDto();
                sseMessageDto.setUserIds(List.of(Long.valueOf(documentParams.getCreateBy())));
                sseMessageDto.setMessage("{\"documentId\":\""+jkDocument.getDocumentId()+"\",\"message\":\"error\",\"progress\":0,\"status\":\"2\"}");
                sseEmitterManager.publishMessage(sseMessageDto);
                // ========== 核心：跳出本次循环，进入下次循环 ==========

                continue;
            }
            JkDocumentParams newJk = JkDocumentParams.builder()
                .documentId(jkDocument.getDocumentId())
                .userId(documentParams.getUserId())
                .tenantId(documentParams.getTenantId())
                .name(jkDocument.getName())
                .customSeparator(jkDocument.getCustomSeparator())
                .knowledge_id(jkDocument.getKnowledgeId())
                .ossId(jkDocument.getOssId())
                .parsingType(jkDocument.getParsingType())
                .url(jkDocument.getUrl())
                .remark(jkDocument.getRemark())
                .standardClassification(jkDocument.getStandardClassification())
                .suffix(jkDocument.getSuffix()).build();

            // 封装任务参数（仅oldFlg=false时执行）
            String taskContent = JSON.toJSONString(new HashMap<String, Object>() {{
                put("taskId", taskId);
                put("documentParams", newJk);
                put("userId", LoginHelper.getUserId());
                put("submitTime", System.currentTimeMillis());
                put("submitTime", System.currentTimeMillis());
            }});

            // 定义任务执行逻辑（仅oldFlg=false时执行）
            Runnable task = () -> {
                logger.info("=== 开始执行任务 === 任务ID: {}, 租户ID: {}", taskId, tenantId);
                long startTime = System.currentTimeMillis();
                try {
                    jkDocumentMapper.updateDocument(JkDocument.builder().documentId(jkDocument.getDocumentId()).status("0").build());

                    logger.info("任务 {} 生成文档ID: {}", taskId, jkDocument.getDocumentId());

                    // 2. 准备上传参数（无需再判断oldFlg，因为主线程已过滤）
                    List<UploadDocumentParams> params = new ArrayList<>();
                    UploadDocumentParams param = new UploadDocumentParams();
                    param.setDocument_id(jkDocument.getDocumentId());
                    param.setName(jkDocument.getName()+"."+jkDocument.getSuffix());
                    param.setUrl(jkDocument.getUrl());
                    param.setParsingType(jkDocument.getParsingType());
                    param.setCustomSeparator(jkDocument.getCustomSeparator());
                    param.setRemark(jkDocument.getRemark());
                    param.setUserId(documentParams.getUserId());
                    param.setTenantId(documentParams.getTenantId());
                    param.setStandardClassification(jkDocument.getStandardClassification());
                    param.setSliceValue(jkDocument.getSliceValue());

                    // 主线程已处理oldDocument参数，直接赋值即可
                    if (flg) {

                        if (null != oldDocument && !oldDocument.getDocumentId().equals(jkDocument.getDocumentId())) {
                            // 若oldFlg=false，仍保留原有参数赋值逻辑
                            param.setOldDocumentId(oldDocument.getDocumentId());
                            param.setOldKnowledgeId(oldDocument.getKnowledgeId());
                        }
                    }
                    if(jkDocument.getSuffix().contains("md")) {
                        param.setMarkDownFlg("true");
                    }
                    params.add(param);

                    //调用Python分析文件
                    JkDocumentSettings settings = new JkDocumentSettings();
                    settings.setKnowledgeId(jkDocument.getKnowledgeId());

                    logger.info("任务 {} 开始调用Python分析文件", taskId);
                    analysisFile(params, settings, "upload");
                    logger.info("任务 {} Python分析完成", taskId);

                    long costTime = System.currentTimeMillis() - startTime;
                    logger.info("=== 任务执行成功 === 任务ID: {}, 耗时: {}ms", taskId, costTime);

                } catch (Exception e) {
                    long costTime = System.currentTimeMillis() - startTime;
                    logger.error("=== 任务执行失败 === 任务ID: {}, 耗时: {}ms, 错误: {}",
                        taskId, costTime, e.getMessage(), e);
                    throw new RuntimeException("任务执行失败: " + e.getMessage(), e);
                }
            };

            try {
                logger.info("任务 {} 提交到限流器", taskContent);
                dynamicRateLimiter.submitTask(tenantId, taskContent, task,flg);
                logger.info("任务 {} 提交成功", taskId);
            } catch (Exception e) {
                logger.error("任务 {} 提交异常", taskId, e);
                return CompletableFuture.completedFuture(0);
            }
        }
        return CompletableFuture.completedFuture(1);
    }



    @Override
    public void updateDocumentByPython(JkDocumentPythonParams parms) {
        if(parms.getStatus().equals("1")) {
            JkDocument bmDocument = jkDocumentMapper.selectTargetDocument(JkDocument.builder().documentId(parms.getDocumentId()).build());
            if( null != bmDocument.getMarkOssId()) {
                int markSize = jkDocumentMapper.selectMarkDownFileExit(parms.getDocumentId(),bmDocument.getMarkOssId());
                if(markSize == 0 ) {
                    sysOssService.deleteWithValidByIds(List.of(Long.valueOf(bmDocument.getMarkOssId())),false);
                }
            }
            //释放队列
            dynamicRateLimiter.releaseOneQueueTask(parms.getTenantId());
        }
        JkDocument document = JkDocument.builder()
            .documentId(parms.getDocumentId())
            .knowledgeId(parms.getKnowledgeId())
            .status(parms.getStatus())
            .sliceTotal(parms.getSliceTotal())
            .length(parms.getLength())
            .wordNum(parms.getWordNum())
            .markOssId(parms.getOssId())
            .markUrl(parms.getMdMinIOUrl())
            .pdfUrl(parms.getPdfUrl())
            .errorMessage(parms.getErrorMessage())
            .build();
        jkDocumentMapper.updateDocument(document);
        updateKnowledgeSize(parms.getKnowledgeId());
        setRedisCache(parms.getKnowledgeId());
    }

    private void updateKnowledgeSize(String knowledgeId) {
        JkKnowledge info  = jkKnowledgeMapper.selectTargetKnowledge(JkKnowledge.builder().knowledgeId(knowledgeId).build());
        List<JkDocument> documentList = jkDocumentMapper.selectDocumentList(JkDocument.builder().knowledgeId(knowledgeId).build());
        Set<String> ALLOWED_DOC_STATUS = Set.of("1", "4", "5", "6");
        List<JkDocument> filteredDocList = documentList.stream()
            .filter(doc -> doc != null // 防护列表中的 null 元素
                && doc.getStatus() != null // 防护 status 为 null
                && ALLOWED_DOC_STATUS.contains(doc.getStatus()))
            .collect(Collectors.toList());
        int totalLen = filteredDocList.stream()
                .mapToInt(doc -> doc.getLength() != null ? doc.getLength() : 0)
                .sum();
        int totalWordNum = filteredDocList.stream()
                .mapToInt(doc -> doc.getWordNum() != null ? doc.getWordNum() : 0)
                .sum();

        int docSize = filteredDocList.size();
        //Long updateBy = LoginHelper.getLoginUser() == null ? null:LoginHelper.getLoginUser().getUserId();
        JkKnowledge paramInfo = JkKnowledge.builder().knowledgeId(knowledgeId)
                .length(totalLen)       // 文件大小
                .wordNum(totalWordNum)  // 总字符数
                .documentSize(docSize) // 文件总数
                .build();
        //paramInfo.setUpdateBy(updateBy);
        paramInfo.setUpdateTime(new Date());
        jkKnowledgeMapper.updateKnowledge(paramInfo);
    }

    private void updateDocumentSize(String knowledgeId,String documentId) {
        List<JkSliceInfo> sliceList = selectByDocumentId(documentId,null);
        //字符数
        int totalLen = sliceList.stream()
                .mapToInt(slice -> slice.getSliceText() != null ? slice.getSliceText().length() : 0)
                .sum();
        //文件大小
        int totalSize = sliceList.stream()
                .mapToInt(slice -> slice.getSliceText() != null ? slice.getSliceText().getBytes(StandardCharsets.UTF_8).length : 0)
                .sum();
        JkDocument doc = JkDocument.builder()
                .documentId(documentId)
                .length(totalSize)
                .wordNum(totalLen)
                .sliceTotal(sliceList.size())
                .build();
        Long updateBy = LoginHelper.getLoginUser() == null ? null:LoginHelper.getLoginUser().getUserId();
        doc.setUpdateBy(updateBy);
        doc.setUpdateTime(new Date());
        jkDocumentMapper.updateDocument(doc);
        updateKnowledgeSize(knowledgeId);
    }

    @Override
    public int updateDocument(JkDocumentParams documentParams, String documentId) {
        int i = jkDocumentMapper.updateDocument(JkDocument.builder()
                .documentId(documentId)
                .name(documentParams.getName())
                .qaChecked(documentParams.getQaChecked())
                .parentGenerationEnabled(documentParams.getParentGenerationEnabled())
                .summaryGenerationEnabled(documentParams.getSummaryGenerationEnabled())
                .relatedQuestionsEnabled(documentParams.getRelatedQuestionsEnabled())
                .status(documentParams.getStatus())
                .build());
        return i;
    }

    @Override
    public List<JkDocument> documentList(JkDocumentParams documentParams) {
        return jkDocumentMapper.selectDocumentList(JkDocument.builder().knowledgeId(documentParams.getKnowledge_id()).build());
    }

    @Override
    public IPage<JkDocument> documentPageList(JkDocumentParams documentParams, PageQuery pageQuery) {
        List<String> list = new ArrayList<>();
        list.add(documentParams.getStatus());
        if(documentParams.getStatus().equals("0")) {
            list.add("2");
            list.add("3");
        } else {
            list.add("4");
            list.add("5");
            list.add("6");
        }
        documentParams.setDocumentStatus(list);
        return jkDocumentMapper.selectDocumentPageList(pageQuery.build(),documentParams);
        //return jkDocumentMapper.selectDocumentPageList(JkDocument.builder().knowledgeId(documentParams.getKnowledge_id()).status(documentParams.getStatus()).build(),pageQuery.build());
    }

    @Override
    public IPage<JkQuoteDocument> quoteDocumentPageList(JkDocumentParams documentParams, PageQuery pageQuery) {
        documentParams.setCreateBy(String.valueOf(LoginHelper.getUserId()));
        return jkDocumentMapper.selectQuoteDocumentPageList(documentParams,pageQuery.build());
    }

    @Override
    public Page<JkSliceDocument>
    sliceDocumentPageList(JkDocumentParams documentParams, PageQuery pageQuery) {
        documentParams.setCreateBy(String.valueOf(LoginHelper.getUserId()));
        return jkDocumentMapper.selectSliceDocumentPageList(pageQuery.build(),documentParams);

    }

    @Override
    public JkDocument documentDetail(String documentId) {
        return jkDocumentMapper.selectTargetDocument(JkDocument.builder().documentId(documentId).build());
    }

    @Override
    public int delDocument(String documentId) {
        JkDocument vo = jkDocumentMapper.selectTargetDocument(JkDocument.builder().documentId(documentId).build());
        if(vo != null){
            String url = deepseekConfig.getBaseurl() + deepseekConfig.getDeleteDoc() + "/" + documentId + "/" + vo.getKnowledgeId();
            Request request = buildDeleteRequest(url);
            OkHttpClient client = buildOkHttpClient();
            try {
                Response response = client.newCall(request).execute();
                if (response.isSuccessful()) {
                    String body = response.body().string();
                    logger.info("删除知识文件调用python接口返回结果：{}", body);
                    JSONObject obj = JSON.parseObject(body);
                    Integer code = obj.getInteger("code");
                    if (code == 200) {
                        List<JkDocument> urlDocuments = jkDocumentMapper.selectDocumentList(JkDocument.builder().ossId(vo.getOssId()).build());
                        //如果没有多个文档引用则删除上传oss文件
                        if (!(urlDocuments.size() > 1)) {
                            logger.info("删除minio文件");
                            sysOssService.deleteWithValidByIds(List.of(Long.valueOf(vo.getOssId())), false);
                        }
                        List<JkMediaReplacement> list = jkMediaReplacementMapper.selectMediaList(JkMediaReplacement.builder().documentId(documentId).build());
                        // 删除图片
                        jkMediaReplacementMapper.deleteMedia(documentId);
                        if (list != null && list.size() > 0) {
                            //list.stream()
                            for (int k = 0; k < list.size(); k++) {
                                logger.info("删除minio图片{}", k);
                                if(null != list.get(k).getOssId()) {
                                    sysOssService.deleteWithValidByIds(List.of(Long.valueOf(list.get(k).getOssId())), false);
                                }
                            }
                        }
                        // 删除document文件
                        int i = jkDocumentMapper.delDocument(documentId);
                        //删除切片数据
                        jkSliceInfoMapper.delByDocId(documentId);
                        if (i > 0) {
                            //更新知识库
                            updateKnowledgeSize(vo.getKnowledgeId());
                        }
                    }
                }
            } catch (IOException e) {
                logger.error("删除知识文件调用python接口失败", e.getMessage());
            }
        }
        return 0;
    }

    @Override
    public int delUnfinishedDocument(String documentId) {
        JkDocument vo = jkDocumentMapper.selectTargetDocument(JkDocument.builder().documentId(documentId).build());
        if(vo != null){
            String url = deepseekConfig.getBaseurl() + deepseekConfig.getCancelTask() + "/" + documentId ;
            JSONObject json = new JSONObject();
            //重新设置knowledgeIds为数组
            json.put("documentId",documentId);

            RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8"), json.toJSONString());
            Request request = buildPostRequest(url,requestBody);
            OkHttpClient client = buildOkHttpClient();
            try {
                Response response = client.newCall(request).execute();
                if (response.isSuccessful()) {
                    String body = response.body().string();
                    logger.info("删除知识文件调用python接口返回结果：{}", body);
                    JSONObject obj = JSON.parseObject(body);
                    Integer code = obj.getInteger("code");
                    if (code == 200) {
                        List<JkDocument> urlDocuments = jkDocumentMapper.selectDocumentList(JkDocument.builder().ossId(vo.getOssId()).build());
                        //如果没有多个文档引用则删除上传oss文件
                        if(!(urlDocuments.size() > 1)){
                            logger.info("删除minio文件");
                            sysOssService.deleteWithValidByIds(List.of(Long.valueOf(vo.getOssId())),false);
                        }
                        List<JkMediaReplacement> list = jkMediaReplacementMapper.selectMediaList(JkMediaReplacement.builder().documentId(documentId).build());
                        // 删除图片
                        jkMediaReplacementMapper.deleteMedia(documentId);
                        if(list != null && list.size() > 0){
                            //list.stream()
                            for (int k=1; k<=list.size(); k++ ) {
                                logger.info("删除minio图片{}", k);
                                sysOssService.deleteWithValidByIds(List.of(Long.valueOf(list.get(k-1).getOssId())),false);
                            }
                        }
                        // 删除document文件
                        int i = jkDocumentMapper.delDocument(documentId);
                        //删除切片数据
                        jkSliceInfoMapper.delByDocId(documentId);
                        if(i > 0){
                            //更新知识库
                            updateKnowledgeSize(vo.getKnowledgeId());
                        }
                    }
                }
            } catch (IOException e) {
                logger.error("删除知识文件调用python接口失败", e.getMessage());
            }
        }
        return 0;
    }

    @Override
    public void uploadUrl(JkDocumentObject object) {

    }

    @Override
    public int createApplication(JkApplicationParams params, boolean isAdmin) {
        ApprovalNode vo = getFirstApproverNode(); // 获取第一个审批对象
        JkApplication jkApplication = jkApplicationMapper.selectTargetApplication(JkApplication.builder().name(params.getName()).build());
        if(null != jkApplication) {
            throw new RuntimeException("应用名称已存在");
        }
        if(vo != null){
            SnowflakeDigitGenerator snowflakeDigitGenerator = new SnowflakeDigitGenerator(1, 1);
            long appId = snowflakeDigitGenerator.nextId();
            JkApplication info = JkApplication.builder().appId(String.valueOf(appId))
                    .name(params.getName())
                    .desc(params.getDesc())
                    .model(params.getModel())
                    .knowledgeIds(params.getKnowledge_ids().stream().collect(Collectors.joining(",")))
                    .topP(params.getTopP())
                    .topK(params.getTopK())
                    .temperature(params.getTemperature())
                    .maxToken(params.getMax_token())
                    .prompt(base64DecodeUtil.decodeIfNeeded(params.getPrompt()))
                    .groupVisible(params.getGroupVisible())
                    .knowledgeInfo(params.getKnowledge_info())
                    .sliceCount(params.getSlice_count())
                    .typeId(params.getTypeId())
                    .visible(params.getVisible())
                    .iconColor(params.getIconColor())
                    .iconType(params.getIconType())
                    .isDeepThink(params.getIsDeepThink())
                    .sort(params.getSort())
                    .build();
            //关联项目
            if(params.getAppProId() != null){
                SysProjectApp sysProjectApp = new SysProjectApp();
                sysProjectApp.setProjectId(params.getAppProId());
                sysProjectApp.setAppId(info.getAppId());
                sysProjectAppMapper.insertSysProjectApp(sysProjectApp);
            }
            //管理员，私有，项目级应用不需要审核
            if(isAdmin
                    || "1".equals(params.getVisible())
                    || AppTypeEnum.PROJECT.getCode().equals(String.valueOf(params.getTypeId()))){ // 1 私有 0 公开
                info.setStatus("3"); // 有直接创建权限无需审核
            }else{
                info.setStatus("2"); // 未开始审核状态
                info.setApprover(String.valueOf(vo.getApprover()));
                info.setNodeOrder(String.valueOf(vo.getNodeOrder()));
            }
            info.setCreateBy(Long.valueOf(params.getUserId()));
            int i = jkApplicationMapper.insertApplication(info);
            if (i > 0) {
                //添加VIP用户
                if(params.getVipList() != null && params.getVipList().size() > 0){
                    for(SysUser sysUser : params.getVipList()){
                        VipApplication vipApplication =
                                VipApplication.builder()
                                        .appId(info.getAppId())
                                        .userId(String.valueOf(sysUser.getUserId()))
                                        .build();
                        jkApplicationMapper.insertVipApp(vipApplication);
                    }
                }

//                TypeApp appType = new TypeApp();
//                appType.setTypeId(params.getTypeId() == null ? null : params.getTypeId());
//                appType.setAppId(String.valueOf(appId));
//                appTypeMapper.insertAppType(appType);

                logger.info("create application success, id:{}, name:{}", appId, params.getName());
                JkAppInfo appInfo = new JkAppInfo();
                appInfo.setAppId(String.valueOf(appId));
                appInfo.setAppInfo(String.valueOf(JSONObject.from(info).toJSONString()));
                jkAppInfoMapper.insertAppInfo(appInfo);

                List<String> list = params.getQuestionList();
                if (!list.isEmpty() && list.size() > 0) {
                    for (int a = 0; a < list.size(); a++) {
                        String str =  list.get(a);
                        if (org.apache.commons.lang3.StringUtils.isNotBlank(str)) {
                            String uuid = IdUtils.simpleUUID();
                            JkQuestion question = JkQuestion.builder().id(uuid).question(str).appId(String.valueOf(appId)).questionIndex(a).build();
                            jkQuestionMapper.insertQuestion(question);
                        }
                    }
                }
                return i;
            }
        }else{
            // 没有审批人
            return 9;
        }
        return 0;
    }

    @Override
    public int updateApplication(JkApplicationParams params, String appId, boolean isAdmin) {
        ApprovalNode vo = getFirstApproverNode(); // 获取第一个审批对象
        JkApplication jkApplication = jkApplicationMapper.selectTargetApplication(JkApplication.builder().name(params.getName()).build());
        if(null != jkApplication) {
            if (!appId.equals(jkApplication.getAppId())) {
                throw new RuntimeException("应用名称已存在");
            }
        }
        if(vo != null){
            JkApplication info = JkApplication.builder().appId(appId)
                    .name(params.getName())
                    .desc(params.getDesc())
                    .model(params.getModel())
                    .knowledgeIds(params.getKnowledge_ids().stream().collect(Collectors.joining(",")))
                    .topP(params.getTopP())
                    .topK(params.getTopK())
                    .temperature(params.getTemperature())
                    .maxToken(params.getMax_token())
                    .prompt(base64DecodeUtil.decodeIfNeeded(params.getPrompt()))
                    .knowledgeInfo(params.getKnowledge_info())
                    .sliceCount(params.getSlice_count())
                    .typeId(params.getTypeId())
                    .isDeepThink(params.getIsDeepThink())
                    .visible(params.getVisible())
                    .iconColor(params.getIconColor())
                    .iconType(params.getIconType())
                    .groupVisible(params.getGroupVisible())
                    .sort(params.getSort())
                    .build();
            //关联项目
            if(params.getAppProId() != null){
                SysProjectApp  sysProjectApp = sysProjectAppMapper.selectSysProjectAppByAppId(appId);
                if(sysProjectApp != null){
                    sysProjectAppMapper.deleteSysProjectAppByAppId(appId);
                }
                sysProjectApp = new SysProjectApp();
                sysProjectApp.setProjectId(params.getAppProId());
                sysProjectApp.setAppId(appId);
                sysProjectAppMapper.insertSysProjectApp(sysProjectApp);
            }
            if(isAdmin  || "1".equals(params.getVisible())){
                info.setStatus("3"); // 有直接修改权限无需审核
                info.setApprover(null);
                info.setNodeOrder(null);
            }else{
                info.setStatus("2"); // 未开始审核状态
                info.setApprover(String.valueOf(vo.getApprover()));
                info.setNodeOrder(String.valueOf(vo.getNodeOrder()));
            }
            info.setUpdateBy(Long.valueOf(params.getUserId()));
            int i = jkApplicationMapper.updateApplication(info);
            if (1 > 0) {
                //添加VIP用户
                jkApplicationMapper.deleteVipByAppId(info.getAppId());
                if(params.getVipList() != null && params.getVipList().size() > 0){
                    for(SysUser sysUser : params.getVipList()){
                        VipApplication vipApplication =
                                VipApplication.builder()
                                        .appId(info.getAppId())
                                        .userId(String.valueOf(sysUser.getUserId()))
                                        .build();
                        jkApplicationMapper.insertVipApp(vipApplication);
                    }
                }

                JkAppInfo appInfo = new JkAppInfo();
                appInfo.setAppId(appId);
                appInfo.setAppInfo(String.valueOf(JSONObject.from(info).toJSONString()));
                jkAppInfoMapper.updateAppInfoByAppId(appInfo);
                logger.info("update application success, id:{}, name:{}", appId, params.getName());
                //预设问题写入数据库
                jkQuestionMapper.delQuestionByAppId(appId);
                List<String> list = params.getQuestionList();
                if (!list.isEmpty() && list.size() > 0) {
                    for (int a = 0; a < list.size(); a++) {
                        String str =  list.get(a);
                        if (org.apache.commons.lang3.StringUtils.isNotBlank(str)) {
                            String uuid = IdUtils.simpleUUID();
                            JkQuestion question = JkQuestion.builder().id(uuid).question(str).appId(String.valueOf(appId)).questionIndex(a).build();
                            jkQuestionMapper.insertQuestion(question);
                        }
                    }
                }
            }
            return i;
        } else {
            return 9;
        }
    }

    @Override
    public List<JkApplicationResult> getApplicationList(JkApplication params) {
        //管理员 获取所有项目
        Map<String,Object> userParams = getUserParams();
        if(userParams != null){
            params.setParams(userParams);
        }
        List<JkApplicationResult> appList = jkApplicationMapper.selectApplicationList(params);
        //添加应用VIP用户
//        if(appList != null && appList.size() > 0){
//            for (JkApplicationResult appResult : appList) {
//                List<SysUser> vipList = userService.selectAppVipList(appResult.getAppId());
//                appResult.setVipList(vipList);
//            }
//        }
        return appList;
    }


    @Override
    public JSONObject selectApplication(String appId) {
        JSONObject object = new JSONObject();
        JkApplication application = JkApplication.builder().appId(appId).build();
        JkApplication jkApplication = jkApplicationMapper.selectTargetApplication(application);
        //项目级应用 返回['项目分类','项目id']
        if(jkApplication != null) {
            //添加应用VIP用户
            List<SysUser> vipList = userService.selectAppVipList(appId);
            jkApplication.setVipList(vipList);

            if(AppTypeEnum.PROJECT.getCode().equals(String.valueOf(jkApplication.getTypeId()))) {
                SysProjectApp sysProjectApp = sysProjectAppMapper.selectSysProjectAppByAppId(application.getAppId());
                if (sysProjectApp != null) {
                    SysProject sysProject = sysProjectMapper.selectSysProjectByProjectId(sysProjectApp.getProjectId());
                    jkApplication.setAppProId(String.valueOf(sysProject.getProjectId())
                            );
                }
            }
            List<String> idList = Arrays.stream(jkApplication.getKnowledgeIds().split(",")) // 按「逗号+空格」拆分字符串为数组
                    .filter(str -> str != null && !str.trim().isEmpty()) // 过滤 null 和空字符串（防异常）
                    .collect(Collectors.toList());
            JSONObject detailObj = JSONObject.from(jkApplication); // 转为JSON对象
            detailObj.put("knowledgeIds", idList); // 用数组替换原字符串
            object.put("detail", detailObj);
        } else {
            object.put("detail", jkApplication);
        }
        List<JkQuestionDTO> list = jkQuestionMapper.getQuestionList(appId);
        object.put("questionlist", list);
        return object;
    }

    @Override
    public int delApplication(String appId) {
        int i = jkApplicationMapper.delApplication(appId);
        if (i > 0) {
            jkAppInfoMapper.delAppInfoByAppId(appId);
            jkQuestionMapper.delQuestionByAppId(appId);
            jkApplicationMapper.delApplication(appId);
        }
        return i;
    }

    @Override
    public List<JkQuestionDTO> selectQuestionByAppId(String appId) {
        List<JkQuestionDTO> List = jkQuestionMapper.getQuestionList(appId);
        return List;
    }

    @Override
    public List<JkKnowledge> queryKnowledgeList() {
        //管理员 获取所有项目
        JkKnowledge params = getKnowledgeUserParams();
        return jkKnowledgeMapper.selectKnowledgeList(params);
    }

    private JkKnowledge getKnowledgeUserParams() {
        JkKnowledge params = null;
        //管理员 获取所有项目
        Map<String,Object> userParams = getUserParams();
        if(userParams != null){
            params = JkKnowledge.builder().build();
            params.setParams(userParams);
        }
        return params;
    }

    private Map<String,Object> getUserParams() {
        //管理员 获取所有项目

        LoginUser user =  null;
        if(StpUtil.isLogin()){
            user = LoginHelper.getLoginUser();
        }
        Map<String,Object> userParams = null;
        if(user != null){
            userParams = new HashMap<>();

            userParams.put("userType",user.getUserType());
            userParams.put("userId",user.getUserId());
            userParams.put("tenantId",user.getTenantId());
            if(LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
                userParams.put("isAdmin", true);
            } else {
                userParams.put("isAdmin", false);
            }
        }
        return userParams;
    }

    @Override
    public List<JkDialogRespDTO> selectDialogListByAppId(String appId, String userId) {
        List<JkDialogRespDTO> list = jkDialogMapper.selectDialog(appId, userId);
//        List<Object> result = setDialogList(list);
        return list;
    }

    @Override
    public Page<JkDialogRespDTO> selectDialogPageListByAppId(String appId, String userId,PageQuery pageQuery) {
        Page<JkDialogRespDTO> list = jkDialogMapper.selectDialogPage(appId, userId,pageQuery.build());
//        List<Object> result = setDialogList(list);
        return list;
    }

    @Override
    public JkDocumentSettings documentSetting(String documentId) {
        return jkDocumentSettingsMapper.selectById(documentId);
    }

    @Override
    public int updateDocumentSetting(JkDocumentParams documentParams, String documentId) {
        JkDocument document = jkDocumentMapper.selectTargetDocument(JkDocument.builder().documentId(documentId).build());
//        List<UploadDocumentParams> listParams = new ArrayList<>();
//        if (document != null) {
//            UploadDocumentParams params = new UploadDocumentParams();
//            params.setDocument_id(documentId);
//            params.setName(document.getName());
//            params.setUrl(document.getUrl());
//            params.setParsingType(documentParams.getParsingType());
//            params.setCustomSeparator(documentParams.getCustomSeparator());
//            params.setUserId(String.valueOf(documentParams.getUserId()));
//            params.setTenantId(documentParams.getTenantId());
//            listParams.add(params);
//            if(document.getParsingType().equals(documentParams.getParsingType())
//                && document.getCustomSeparator().equals(documentParams.getCustomSeparator())
//                && document.getStandardClassification().equals(documentParams.getStandardClassification())) {
//                return 0;
//            }
//        }
//
//        JkDocumentSettings settings = new JkDocumentSettings();
//        settings.setKnowledgeId(documentParams.getKnowledge_id());
//        document.setParsingType(documentParams.getParsingType());
//        document.setStandardClassification(documentParams.getStandardClassification());
//        document.setCustomSeparator(documentParams.getCustomSeparator());
//        document.setUpdateBy(LoginHelper.getUserId());
//        document.setStatus("3");
//        document.setCustomSeparator(documentParams.getCustomSeparator());
//        jkDocumentMapper.updateDocument(document);
        //analysisFile(listParams, settings, "update");
        if(document != null){
            String url = deepseekConfig.getBaseurl() + deepseekConfig.getDeleteDoc() + "/" + documentId + "/" + document.getKnowledgeId();
            Request request = buildDeleteRequest(url);
            OkHttpClient client = buildOkHttpClient();
            try {
                Response response = client.newCall(request).execute();
                if (response.isSuccessful()) {
                    String body = response.body().string();
                    logger.info("删除知识文件调用python接口返回结果：{}", body);
                    JSONObject obj = JSON.parseObject(body);
                    Integer code = obj.getInteger("code");
                    if (code == 200) {
                        List<JkMediaReplacement> list = jkMediaReplacementMapper.selectMediaList(JkMediaReplacement.builder().documentId(documentId).build());
                        // 删除图片
                        jkMediaReplacementMapper.deleteMedia(documentId);
//                        if (list != null && list.size() > 0) {
//                            //list.stream()
//                            for (int k = 1; k <= list.size(); k++) {
//                                logger.info("删除minio图片{}", k);
//                                sysOssService.deleteWithValidByIds(List.of(Long.valueOf(list.get(k-1).getOssId())), false);
//                            }
//                        }
                        //删除切片数据
                        jkSliceInfoMapper.delByDocId(documentId);

                    }
                }
            } catch (IOException e) {
                logger.error("删除知识文件调用python接口失败", e.getMessage());
            }
        }
        return 1;
    }

    @Override
    public JSONObject getSliceList(JkSliceParams params) {

//        String url = deepseekConfig.getBaseurl() + deepseekConfig.getSlicePage();
//        JSONObject jsonObject = new JSONObject();
//        jsonObject.put("document_id", params.getDocument_id());
//        jsonObject.put("knowledge_id", params.getKnowledge_id());
//        jsonObject.put("text", params.getText());
//        jsonObject.put("pageNum", params.getPageNum());
//        jsonObject.put("pageSize", params.getPageSize());
//
//        RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8"), jsonObject.toJSONString());
//        Request request = buildPostRequest(url, requestBody);
//
//        OkHttpClient client = buildOkHttpClient();
//        Response response = null;
//        try {
//            response = client.newCall(request).execute();
//            if (response.isSuccessful()) {
//                String body = response.body().string();
//                JSONObject obj = JSON.parseObject(body);
//                Integer code = obj.getInteger("code");
//                if (code == 200) {
//                    return obj;
//                }
//            } else {
//                logger.info("获取切片列表调用python接口失败,返回状态码：{}", response.code());
//            }
//        } catch (IOException e) {
//            logger.error("获取切片列表调用python接口失败", e.getMessage());
//        }
        return null;
    }

    @Override
    public int deleteSlice(String sliceId, String knowledgeId, String documentId) {
        String url = deepseekConfig.getBaseurl() + deepseekConfig.getDeleteSlice() + "/" + sliceId + "/" + knowledgeId +  "/" + documentId;
        Request request = buildDeleteRequest(url);
        OkHttpClient client = buildOkHttpClient();
        Response response = null;
        try {
            response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                String body = response.body().string();
                JSONObject obj = JSON.parseObject(body);
                Integer code = obj.getInteger("code");
                logger.info("删除切片调用python接口成功,返回结果：{}", body);
                if (code == 200) {
                    //updateDocumentSize(knowledgeId,documentId);
                    return 1;
                }
            }
        } catch (IOException e) {
            logger.error("删除切片调用python接口失败", e.getMessage());
        }

        return 0;
    }

    @Override
    public JSONObject getSliceDetail(String sliceId, String knowledgeId) {
        String url = deepseekConfig.getBaseurl() + deepseekConfig.getSliceDetail() + "/" + knowledgeId + "/" + sliceId;
        Request request = buildGetRequest(url);

        OkHttpClient client = buildOkHttpClient();
        try {
            Response response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                String body = response.body().string();
                log.info("查询切片详情返回：" + body);
                JSONObject jsonObject = JSON.parseObject(body);
                JSONObject dataObject = jsonObject.getJSONObject("data");
                String docId = dataObject.getString("document_id");
                String sliceText = dataObject.getString("slice_text");
                JkMediaReplacement conditonR = JkMediaReplacement.builder().documentId(docId).build();
                List<JkMediaReplacement> imageList = jkMediaReplacementMapper.selectMediaList(conditonR);
                //替换【示意图序号_XXXXX】为超链接
                List<JkSliceImage> sliceImageList = SchematicNumberParser.extractImageUrlMappings(sliceText,imageList);
//                String replaceText = SchematicNumberParser.replaceWithImageLinks(sliceText,imageList);
//                dataObject.put("slice_text",replaceText);
                dataObject.put("imageList",sliceImageList);
                return jsonObject;
            } else {
                logger.info("获取切片详情调用python接口失败,返回状态码：{}", response.code());
            }
        } catch (IOException e) {
            log.error("查询切片详情调用python接口失败", e.getMessage());
        }
        return null;
    }

    @Override
    public JkMediaReplacement uploadSliceImage(JkSliceImpageParms parms, String knowledgeId, String documentId) {
        String replace_text = "【示意图序号_"+documentId+"_"+System.currentTimeMillis()+"_"+1+"】" ;
        //保存图片信息
        JkMediaReplacement  jkMediaReplacement =
            JkMediaReplacement.builder()
                .knowledgeId(knowledgeId)
                .documentId(documentId)
                .originText(replace_text)
                .ossId(String.valueOf(parms.getOssId()))
                .mediaType("image")
                .mediaUrl(parms.getUrl())
                .build();
        Date currentDate = new Date();
        jkMediaReplacement.setCreateTime(currentDate);
        jkMediaReplacement.setCreateBy(LoginHelper.getUserId());
        jkMediaReplacement.setUpdateTime(currentDate);
        jkMediaReplacementMapper.insertMedia(jkMediaReplacement);
        return jkMediaReplacement;
    }

    @Override
    public int updateSliceInfo(JkSliceUpdateParams params,Boolean flg) {
//        if(params.getIndex() != params.getSliceIndex()) {
//            jkSliceInfoMapper.updateSliceIndex(params.getIndex(), params.getSliceId());
//        }
        JkSliceInfo sliceInfo = jkSliceInfoMapper.selectBySliceId(params.getSliceId());
        JkDocument info = jkDocumentMapper.selectTargetDocument(JkDocument.builder().documentId(params.getDocumentId()).build());
        if(!sliceInfo.getSliceText().equals(params.getSliceText()) || !flg) {
            String url = deepseekConfig.getBaseurl() + deepseekConfig.getUpdateSlice();
            JkKnowledge knowledge = jkKnowledgeMapper.selectAllKnowledgeList(JkKnowledge.builder().knowledgeId(params.getKnowledgeId()).build()).get(0);
            JSONObject object = new JSONObject();
            object.put("knowledge_id", params.getKnowledgeId());
            object.put("slice_id", params.getSliceId());
            object.put("slice_text", params.getSliceText());
            object.put("customSeparator", info.getCustomSeparator());
            object.put("document_id", params.getDocumentId());
            object.put("embedding_id",knowledge.getEmbeddingId());
            RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8"), object.toJSONString());

            Request request = buildPutRequest(url, requestBody);
            OkHttpClient client = buildOkHttpClient();
            try {
                Response response = client.newCall(request).execute();
                if (response.isSuccessful()) {
                    String body = response.body().string();
                    JSONObject obj = JSON.parseObject(body);
                    Integer code = obj.getInteger("code");
                    if (code == 200) {
                        //updateDocumentSize(params.getKnowledgeId(),params.getDocumentId());
                        return 1;
                    }
                }
            } catch (Exception e) {
                logger.error("更新切片信息调用python接口失败", e.getMessage());
            }
        }
        return 0;
    }

    @Override
    public JkMediaReplacement getJkMediaReplacement() {
        JkMediaReplacement mrParams = JkMediaReplacement.builder()
                .documentId("a2912832435041734656")
                .originText("【示意图序号_a2912832435041734656_1】")
                .mediaType("image").build();
        JkMediaReplacement mrVo = jkMediaReplacementMapper.selectTargetMedia(mrParams);
        return mrVo;
    }

    @Override
    public String addSlice(JkSliceUpdateParams params) {
        String url = deepseekConfig.getBaseurl() + deepseekConfig.getAddSlice();
        JkDocument info = jkDocumentMapper.selectTargetDocument(JkDocument.builder().documentId(params.getDocumentId()).build());
        if(info != null){
            JkKnowledge knowledge = jkKnowledgeMapper.selectAllKnowledgeList(JkKnowledge.builder().knowledgeId(params.getKnowledgeId()).build()).get(0);
            JSONObject json = new JSONObject();
            json.put("knowledge_id", params.getKnowledgeId());
            json.put("document_id", params.getDocumentId());
            json.put("slice_text", params.getSliceText());
            json.put("doc_name", info.getName());
            json.put("customSeparator", info.getCustomSeparator());
            json.put("slice_index", params.getIndex());
            json.put("embedding_id", knowledge.getEmbeddingId());
            RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8"), json.toJSONString());

            Request request = buildPostRequest(url, requestBody);
            OkHttpClient client = buildOkHttpClient();
            try {
                Response response = client.newCall(request).execute();
                if (response.isSuccessful()) {
                    String body = response.body().string();
                    JSONObject obj = JSON.parseObject(body);
                    String status = obj.getString("status");
                    if (status.equals("insert_success")) {
                        //updateDocumentSize(params.getKnowledgeId(),params.getDocumentId());
                        return obj.getString("slice_id");
                    }
                }
            }catch (Exception e) {
                logger.error("新增切片调用python接口失败", e.getMessage());
            }
        }
        return "0";
    }


    @Override
    public List<JkSliceInfo> selectByDocumentId(String documentId,String sliceText) {
        List<JkSliceInfo> resultList = jkSliceInfoMapper.selectByDocumentId(documentId,sliceText);
        List<JkMediaReplacement> mediaReplacements = jkMediaReplacementMapper.selectMediaList(JkMediaReplacement.builder().documentId(documentId).build());
        for (JkSliceInfo jkSliceInfo : resultList) {
            List<JkMediaReplacement> filterList = mediaReplacements.stream()
                .filter(entity -> {
                    // 避免 originText 为 null 导致空指针
                    String originText = entity.getOriginText();
                    return originText != null && jkSliceInfo.getSliceText().contains(originText);
                })
                .collect(Collectors.toList());
            jkSliceInfo.setMediaList(filterList);
        }
        return resultList;
    }

    @Override
    public List<JkSliceInfo> selectOldSliceByDocumentId(String documentId,String sliceText) {
        List<JkSliceInfo> resultList = jkSliceInfoMapper.selectOldSliceByDocumentId(documentId,sliceText);
        List<JkMediaReplacement> mediaReplacements = jkMediaReplacementMapper.selectMediaList(JkMediaReplacement.builder().documentId(documentId).build());
        for (JkSliceInfo jkSliceInfo : resultList) {
            jkSliceInfo.setSliceText(jkSliceInfo.getOldSliceText());
            List<JkMediaReplacement> filterList = mediaReplacements.stream()
                .filter(entity -> {
                    // 避免 originText 为 null 导致空指针
                    String originText = entity.getOriginText();
                    return originText != null && jkSliceInfo.getSliceText().contains(originText);
                })
                .collect(Collectors.toList());
            jkSliceInfo.setMediaList(filterList);
        }
        return resultList;
    }

    @Override
    public Page<JkSliceInfo> selectPageByDocumentId(String documentId,String sliceText,PageQuery pageQuery) {
        return jkSliceInfoMapper.selectPageByDocumentId(documentId,sliceText,pageQuery.build());
    }

    @Override
    public JkSliceInfo selectBySliceId(String sliceId) {
        return jkSliceInfoMapper.selectBySliceId(sliceId);
    }

    @Override
    public List<SliceInfo> slicePageInfoList(List<String> list) {
        List<SliceInfo> resultList  = jkSliceInfoMapper.slicePageInfoList(list);

        for (SliceInfo jkSliceInfo : resultList) {
            List<JkMediaReplacement> mediaReplacements = jkMediaReplacementMapper.selectMediaList(JkMediaReplacement.builder().documentId(jkSliceInfo.getDocumentId()).build());
            List<JkMediaReplacement> filterList = mediaReplacements.stream()
                .filter(entity -> {
                    // 避免 originText 为 null 导致空指针
                    String originText = entity.getOriginText();
                    return originText != null && jkSliceInfo.getSliceText().contains(originText);
                })
                .collect(Collectors.toList());
            jkSliceInfo.setMediaList(filterList);
        }
        return resultList;
    }

    @Override
    public void delByDocId(String docId) {
        jkSliceInfoMapper.delByDocId(docId);
    }

    @Override
    public void importDocumentList(JkDocumentExportParams params) {
        List<BmDocument> list = jkDocumentMapper.selectDocumentByDocumentIds(params.getDocumentIds());
        String url = deepseekConfig.getBaseurl() + deepseekConfig.getImportDocument();
        JSONObject json = new JSONObject();
        // 使用 Stream API 进行分组和转换
        Map<String, List<String>> map = list.stream()
            // 1. 分组：以 knowledgeId 作为 key
            .collect(Collectors.groupingBy(
                BmDocument::getKnowledgeId,
                // 2. 下游收集器：对每个分组内的元素进行处理
                Collectors.mapping(
                    // 2.1. 投影：将 BmDocument 对象转换为 DocumentIdDto 对象
                    doc -> doc.getDocumentId(),
                    // 2.2. 收集：将投影后的对象收集到 List 中
                    Collectors.toList()
                )
            ));
        json.put("collections_docs_map",map);
        json.put("target_collection",params.getKnowledgeId());
        json.put("createBy",LoginHelper.getUserId());
        json.put("tenantId",LoginHelper.getTenantId());
        RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8"), json.toJSONString());

        Request request = buildPostRequest(url, requestBody);
        OkHttpClient client = buildOkHttpClient();
        try {
            Response response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                String body = response.body().string();
                JSONObject obj = JSON.parseObject(body);
                Integer code = obj.getInteger("code");
                if (code == 200) {
                    updateKnowledgeSize(params.getKnowledgeId());
                }
            }
        } catch (Exception e) {
            logger.error("导入引用文件调用python接口失败", e.getMessage());
        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSliceInfoList(JkSliceUpdateReq params,Boolean flg) {

        if(null != params.getDelSliceIds()) {
            for(String sliceId : params.getDelSliceIds()) {
                deleteSlice(sliceId,params.getKnowledgeId(), params.getDocumentId());
            }
        }
        for(JkSliceUpdateParams slice:params.getSliceList()) {
            if (null == slice.getSliceId()) {
                slice.setKnowledgeId(params.getKnowledgeId());
                slice.setDocumentId(params.getDocumentId());
                addSlice(slice);
                continue;
            }
            JkSliceInfo jkSliceInfo = jkSliceInfoMapper.selectBySliceId(slice.getSliceId());

            if (!flg || !slice.getSliceText().equals(jkSliceInfo.getSliceText())) {
                updateSliceInfo(slice,flg);
            }else if (slice.getSliceIndex().intValue() != slice.getIndex().intValue()) {
                jkSliceInfoMapper.updateSliceIndex(slice.getIndex(), slice.getSliceId());
            }
        }
    }

    @Override
    public JSONObject searchSlice(String id) {
        String url = deepseekConfig.getBaseurl() + deepseekConfig.getSearchSlice() + "/" + id;
        Request request = buildGetRequest(url);
        OkHttpClient client = buildOkHttpClient();
        try {
            Response response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                String body = response.body().string();
                JSONObject obj = JSON.parseObject(body);
                Integer code = obj.getInteger("code");
                if (code == 200) {
                    return obj;
                }
            }
        }catch (Exception e) {
            logger.error("切片得分调用python接口失败", e.getMessage());
        }
        return null;
    }

    @Override
    public List<Object> searchAppTypeGroupList(List<SysDictDataVo> dictDataList, String userId) {
        List<Object> result =  new ArrayList();
        if (dictDataList!= null && dictDataList.size() > 0) {
            for (SysDictDataVo dictData : dictDataList){
                Map<String, Object> map = new HashMap();
                JkApplication paramsVo = JkApplication.builder().approver(userId).flag("0").addOrderBy("0").build();
                //项目应用
                if(dictData.getDictCode() == 41){
                    Map<String, Object> l = searchAppProjectGroupList(paramsVo, dictData);
                    if(!MapUtils.isEmpty(l)){
                        result.add(l);
                    }
                //其他分类应用
                }else{
                    paramsVo.setTypeId(Long.valueOf(dictData.getDictCode()));
                    List<JkApplicationResult> list = jkApplicationMapper.selectApplicationList(paramsVo);
                    if(list != null && list.size() > 0){
                        List<Object> appInfoList = new ArrayList();
                        map.put("title", dictData.getDictLabel());
                        for (JkApplicationResult application : list){
                            Map<String, Object> infoMap = new HashMap();
                            infoMap.put("title", application.getName());
                            infoMap.put("appId", application.getAppId());
                            infoMap.put("showMenu", "false");
                            infoMap.put("chatMode", "LOCAL");
                            appInfoList.add(infoMap);
                        }
                        map.put("children", appInfoList);
                        result.add(map);
                    }
                }
            }
        }
        return result;
    }

    @Override
    public Page<JkKnowledge> queryKnowledgePageList(JkPageParams jkPageParams,PageQuery pageQuery) {
        //管理员 获取所有项目
        JkKnowledge params = getKnowledgeUserParams();
        params.setName(jkPageParams.getName());
        if(!jkPageParams.getClassificationType()) {
            params.setCreateBy(LoginHelper.getUserId());
        }
        pageQuery.setOrderByColumn("create_time");
        pageQuery.setIsAsc("desc");
        return jkKnowledgeMapper.selectKnowledgePageList(params,pageQuery.build());
    }

    @Override
    public List<KnowledgeSliceVo> selectExportKnowledgeSlice(String knowledgeId) {
        return jkKnowledgeMapper.selectExportKnowledgeSlice(knowledgeId);
    }

    @Override
    public List<KnowledgeSliceVo> selectExportDocumentSlice(List<String> documentIds) {
        return jkDocumentMapper.selectExportDocumentSlice(documentIds);
    }

    @Override
    public void downloadDocument(List<String> documentIds, OutputStream outputStream) throws IOException {
        List<String> urlList = jkDocumentMapper.selectDownloadDocumentUrl(documentIds);
        if (urlList == null || urlList.isEmpty()) {
            throw new IllegalArgumentException("没有找到对应的文件URL");
        }
        doZipOutputStream(urlList,outputStream);
    }

    @Override
    public void downloadFileDocument(List<String> documentIds, OutputStream outputStream) throws IOException {
        List<String> urlList = jkDocumentMapper.selectDownloadDocumentFileUrl(documentIds);
        if (urlList == null || urlList.isEmpty()) {
            throw new IllegalArgumentException("没有找到对应的文件URL");
        }
        doZipOutputStream(urlList,outputStream);
    }

    @Override
    public void downloadKnowledgeFileDocument(String knowledgeId, OutputStream outputStream) throws IOException {
        List<String> urlList = jkDocumentMapper.selectDownloadKnowledgeFileDocument(knowledgeId);
        if (urlList == null || urlList.isEmpty()) {
            throw new IllegalArgumentException("没有找到对应的文件URL");
        }
        // 2. 使用 ZipOutputStream 包装响应输出流
        doZipOutputStream(urlList,outputStream);
    }

    @Override
    public void downloadKnowledgeMarkDownFileDocument(String knowledgeId, OutputStream outputStream) throws IOException {
        List<String> urlList = jkDocumentMapper.selectDownloadKnowledgeMarkDownFileDocument(knowledgeId);
        if (urlList == null || urlList.isEmpty()) {
            throw new IllegalArgumentException("没有找到对应的文件URL");
        }
        // 2. 使用 ZipOutputStream 包装响应输出流
        doZipOutputStream(urlList,outputStream);
    }

    @Override
    public String checkDocumentFile(DocumentFileDTO params,OssParms ossParms) {
        return jkDocumentMapper.selectDocumentByOssIds(params,ossParms.getOssId());
    }

    @Override
    public String selQouteDocumentFile(BmDocument params,String knowledgeId) {
        return jkDocumentMapper.selectQuoteDocument(params,knowledgeId);
    }

    @Override
    public String getMarkDownSliceByDocumentId(String documentId) throws IOException {
        List<String> markUrls = jkDocumentMapper.selectDownloadDocumentUrl(List.of(documentId));
        if(null != markUrls) {
            return downloadMdContent(markUrls.get(0));
        }
        return null;
    }

    @Override
    public void editMarkDownFile(JkMarkUpdateReq req) {
        // 步骤1：生成唯一文件名（避免重复，格式：业务标识_时间戳.md）
        String fileName = "business_report_" + System.currentTimeMillis() + ".md";
        String mdString =  URLDecoder.decode(req.getMarkDownText(), StandardCharsets.UTF_8);
        // 步骤2：转为 MultipartFile 对象
        MultipartFile mdFile = MarkdownGenerator.generateMdMultipartFile(mdString, fileName);
        // 步骤3：调用 upload 方法上传到 MinIO
        SysOssVo sysOssVo = sysOssService.upload(mdFile);
        JkDocument bmDocument = jkDocumentMapper.selectTargetDocument(JkDocument.builder().documentId(req.getDocumentId()).build());
        List<UploadDocumentParams> params = new ArrayList<>();
        UploadDocumentParams param = new UploadDocumentParams();
        param.setDocument_id(req.getDocumentId());
        param.setName(sysOssVo.getOriginalName());
        param.setUrl(sysOssVo.getUrl());
        param.setParsingType(bmDocument.getParsingType());
        param.setCustomSeparator(bmDocument.getCustomSeparator());
        param.setRemark(bmDocument.getRemark());
        param.setUserId(String.valueOf(LoginHelper.getUserId()));
        param.setTenantId(String.valueOf(LoginHelper.getTenantId()));
        param.setStandardClassification(bmDocument.getStandardClassification());
        param.setSliceValue(bmDocument.getSliceValue());
        param.setMarkDownFlg("True");
        params.add(param);

        //调用Python分析文件
        JkDocumentSettings settings = new JkDocumentSettings();
        settings.setKnowledgeId(req.getKnowledgeId());

        analysisFile(params, settings, "upload");

    }

    @Override
    public void updateDocumentKnowledgeSize(String knowlegeId, String documentId) {
        updateDocumentSize(knowlegeId,documentId);
    }

    @Override
    public void cleanLjData() {
        List<String> list = jkKnowledgeMapper.getKnowledgeIds();
        String url = deepseekConfig.getBaseCleanUrl();
        JSONObject json = new JSONObject();
        json.put("knowledge_ids",list);
        RequestBody requestBody = FormBody.create(MediaType.parse("application/json; charset=utf-8"), json.toJSONString());
        Request request = buildPostRequest(url, requestBody);
        OkHttpClient client = buildOkHttpClient();
        try {
            Response response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                String body = response.body().string();
                JSONObject obj = JSON.parseObject(body);
                Integer code = obj.getInteger("code");
                if (code == 200) {
                }
            }
        } catch (Exception e) {
            logger.error("清除脏数据调用python接口失败", e.getMessage());
        }
    }

    @Override
    public void pdfStream(List<String> documentIds, OutputStream outputStream) throws IOException {
        List<String> urlList = jkDocumentMapper.selectPdfUrl(documentIds);
        if (urlList == null || urlList.isEmpty()) {
            throw new IllegalArgumentException("没有找到对应的文件URL");
        }

        byte[] buffer = new byte[1024 * 4]; // 4KB 缓冲区

        for (String fileUrl : urlList) {
            // 为每个URL创建一个连接并获取输入流
            // 使用 try-with-resources 确保 InputStream 被自动关闭
            try (InputStream fis = getInputStreamFromUrl(fileUrl)) {
                int len;
                // 循环读取输入流内容，并写入到输出流中
                while ((len = fis.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, len);
                }
                // 每次写入完成后，刷新输出流，确保数据及时发送
                outputStream.flush();
            }
        }
    }
    /**
     * 从URL中提取文件名
     * @param urlStr URL
     * @return 文件名
     */
    private String extractFileNameFromUrl(String urlStr) {
        try {
            URL url = new URL(urlStr);
            String path = url.getPath();
            return path.substring(path.lastIndexOf('/') + 1);
        } catch (Exception e) {
            // 如果提取失败，可以返回一个默认名称或UUID
            return "unknown_file_" + System.currentTimeMillis();
        }
    }

    private void doZipOutputStream(List<String> urlList,OutputStream outputStream) throws IOException {
        try (ZipOutputStream zos = new ZipOutputStream(outputStream)) {
            byte[] buffer = new byte[1024 * 4]; // 4KB 缓冲区

            for (String fileUrl : urlList) {
                if (StringUtils.isEmpty(fileUrl)) {
                    continue;
                }
                // 为每个文件创建一个 ZipEntry
                // 从URL中提取文件名作为ZipEntry的名称
                String fileName = extractFileNameFromUrl(fileUrl);
                ZipEntry zipEntry = new ZipEntry(fileName);
                zos.putNextEntry(zipEntry);

                // 3. 下载文件并写入 ZipOutputStream
                try (InputStream fis = getInputStreamFromUrl(fileUrl)) {
                    int len;
                    while ((len = fis.read(buffer)) > 0) {
                        zos.write(buffer, 0, len);
                    }
                }
                zos.closeEntry(); // 关闭当前的 ZipEntry
            }
        }
    }

    /**
     * 通过URL获取输入流
     * @param urlStr URL
     * @return 输入流
     * @throws IOException 异常
     */
    private InputStream getInputStreamFromUrl(String urlStr) throws IOException {
        OkHttpClient client = new OkHttpClient.Builder()
            .connectTimeout(15, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .followRedirects(true)
            .build();

        Request request = new Request.Builder().url(urlStr).build();
        Response response = client.newCall(request).execute();

        if (!response.isSuccessful()) {
            throw new IOException("服务器响应失败：" + response.code());
        }
        return response.body().byteStream();
    }


    private Map<String, Object> searchAppProjectGroupList(JkApplication paramsVo, SysDictDataVo dictData) {
        paramsVo.setTypeId(dictData.getDictCode());
        List<JkApplicationResult> list = jkApplicationMapper.selectApplicationList(paramsVo);
        //项目级应用
        Map<String, Object> projectLevelMap = new HashMap();
        projectLevelMap.put("title", dictData.getDictLabel());
        List<Object> projectAppList = new ArrayList();
        if (list!= null && list.size() > 0) {
            // 按项目分组
            Map<String, List<JkApplicationResult>> projectAppMap = list
                    .stream().collect(Collectors.groupingBy(JkApplicationResult::getProjectName));

            for (Iterator<String> iterator = projectAppMap.keySet().iterator(); iterator.hasNext();) {

                String projectName = iterator.next();
                Map<String,Object> newProjectAppMap = new HashMap();
                List<JkApplicationResult> appList = projectAppMap.get(projectName);
                List<Object> newAppList = new ArrayList();
                for (JkApplicationResult application : appList) {
                    Map<String, Object> appMap = new HashMap();
                    appMap.put("appId", application.getAppId());
                    appMap.put("showMenu", "false");
                    appMap.put("chatMode", "LOCAL");
                    appMap.put("title", application.getName());
                    newAppList.add(appMap);
                }
                newProjectAppMap.put("title", projectName);
                newProjectAppMap.put("children",newAppList);
                projectAppList.add(newProjectAppMap);
            }
        }

        projectLevelMap.put("children",projectAppList);
        return projectLevelMap;
    };

    private Map<String, Object> searchAppTypeGroupList(JkApplication paramsVo, SysDictData dictData) {
        //慧监理
        paramsVo.setTypeId(Long.valueOf(42));
        List<JkApplicationResult> list1 = jkApplicationMapper.selectApplicationList(paramsVo);
        //慧项管
        paramsVo.setTypeId(Long.valueOf(43));
        List<JkApplicationResult> list2 = jkApplicationMapper.selectApplicationList(paramsVo);
        List<JkApplicationResult> list3 = new ArrayList();
        list3.addAll(list1);
        list3.addAll(list2);
        Map<String, Object> map = new HashMap();
        if (list3!= null && list3.size() > 0) {
            List<Object> l = new ArrayList<>();
            map.put("title", dictData.getDictLabel());
            // 分组
            Map<String, List<JkApplicationResult>> map1 = list3.stream().collect(Collectors.groupingBy(JkApplicationResult::getTypeId));
            for (Map.Entry<String, List<JkApplicationResult>> entry : map1.entrySet()) {
                List<JkApplicationResult> mp = entry.getValue();
                Map<String, Object> infoProjectMap = new HashMap();
                List<Object> projectInfoList = new ArrayList();
                for (JkApplicationResult application : mp){
                    SysDictData vo = sysDictDataMapper.selectById(Long.valueOf(application.getTypeId()));
                    infoProjectMap.put("title", vo.getDictLabel());
                    Map<String, Object> projectMap = new HashMap();
                    projectMap.put("appId", application.getAppId());
                    projectMap.put("showMenu", "false");
                    projectMap.put("chatMode", "LOCAL");
                    projectMap.put("title", application.getName());
                    projectInfoList.add(projectMap);
                }
                infoProjectMap.put("children", projectInfoList);
                l.add(infoProjectMap);
            }
            map.put("children", l);
        }
        return map;
    }

    @Override
    public List<JkApplication> applicationListByApprover(JkApplicationParams jkApplicationParams) {
        JkApplication vo = JkApplication.builder().approver(jkApplicationParams.getApprover())
                .build();
        return jkApplicationMapper.selectAppListByApprover(vo);
    }

    @Override
    public Page<JkApplication> applicationPageListByApprover(JkApplicationParams jkApplicationParams,PageQuery pageQuery) {
        JkApplication vo = JkApplication.builder().approver(jkApplicationParams.getApprover())
            .build();
        return jkApplicationMapper.selectAppPageListByApprover(vo,pageQuery.build());
    }

    @Override
    public Page<JkApplication> applicationHistoryPageListByApprover(JkApplicationParams jkApplicationParams,PageQuery pageQuery) {
        JkApplication vo = JkApplication.builder().approver(jkApplicationParams.getApprover())
            .build();
        return jkApplicationMapper.selectAppHistoryPageListByApprover(vo,pageQuery.build());
    }

    @Override
    public int startAudit(String appId, String userId) {
        List<DocumentAuditConfig> list = documentAuditConfigMapper.selectDocumentAuditConfigListByBo(null);
        if(list != null && list.size() > 0) {
            ApprovalFlowManager flowManager = new ApprovalFlowManager();
            logger.info("开启审核");
            for (DocumentAuditConfig config : list) {
                // 添加审核节点
                flowManager.addNode(new ApprovalNode(config.getId(), config.getNodeOrder(), config.getApprover(), config.getChain()));
            }

            ApprovalNode vo = flowManager.getFirstApprover();
            JkApplication appVo = jkApplicationMapper.selectTargetApplication(JkApplication.builder().appId(appId).build());
            appVo.setApprover(String.valueOf(vo.getApprover()));
            appVo.setNodeOrder(String.valueOf(vo.getNodeOrder()));
            appVo.setStatus("1");
            int i = jkApplicationMapper.updateApplication(appVo);
            if(i > 0){
                DocumentAuditInfo info = new DocumentAuditInfo();
                info.setAppId(appId);
                info.setApprover(userId);
                info.setStatus("5");
                info.setNodeOrder(Integer.valueOf("0"));
                //info.setCreateBy(userId);
                info.setCreateTime(new Date());
                documentAuditInfoMapper.insertDocumentAuditInfo(info); // 审核记录
            }
            return i;
        }else{
            return 9;
        }
    }

    @Override
    public int updateAuditApplication(JkApplicationParams appParams, String appId) {
//          获取审核配置信息 （人员，节点）
        List<DocumentAuditConfig> list = documentAuditConfigMapper.selectDocumentAuditConfigListByBo(null);
        if(list != null && list.size() > 0){
            if(("Y").equals(appParams.getStatus())){
                ApprovalFlowManager flowManager = new ApprovalFlowManager();
                logger.info("审核通过，开始审批流程");
                for (DocumentAuditConfig config: list){
                    // 添加审核节点
                    flowManager.addNode(new ApprovalNode(config.getId(), config.getNodeOrder(), config.getApprover(), config.getChain()));
                }
                // 根据审核节点获取下一个审核节点
                ApprovalNode nextNode = flowManager.getNextApproverByOrder(Integer.valueOf(appParams.getNodeOrder()));
                if (nextNode == null) {
                    //  审核通过，更新文档状态
                    logger.info("审核通过，到最后节点");
                    return saveAuditInfo(appParams, "3", appId);
                }else{
                    // 审核通过，下一个节点
                    JkApplication appVo = jkApplicationMapper.selectTargetApplication(JkApplication.builder().appId(appId).build());
                    appVo.setApprover(String.valueOf(nextNode.getApprover()));
                    appVo.setNodeOrder(String.valueOf(nextNode.getNodeOrder()));
                    appVo.setStatus("2");
                    appVo.setComment(appParams.getComment());
                    int successI = jkApplicationMapper.updateApplication(appVo);
                    if(successI > 0){
                        DocumentAuditInfo info = new DocumentAuditInfo();
                        info.setAppId(appId);
                        info.setApprover(appParams.getApprover());
                        info.setComment(appParams.getComment());
                        info.setStatus("2");
                        info.setNodeOrder(Integer.valueOf(appParams.getNodeOrder()));
                        //info.setCreateBy(appParams.getApprover());
                        info.setCreateTime(new Date());
                        documentAuditInfoMapper.insertDocumentAuditInfo(info); // 审核记录
                    }
                    return successI;
                }
            }else{
                //  审核拒绝，更新文档状态
                logger.info("审核拒绝，更新状态");
                return saveAuditInfo(appParams, "5", appId);
            }
        }
        return 0;
    }

    private int saveAuditInfo(JkApplicationParams appParams, String status, String appId){
        JkApplication appVo = jkApplicationMapper.selectTargetApplication(JkApplication.builder().appId(appId).build());
        appVo.setApprover(appParams.getApprover());
        appVo.setNodeOrder(appParams.getNodeOrder());
        appVo.setStatus(status);
        appVo.setComment(appParams.getComment());
        int successI = jkApplicationMapper.updateApplication(appVo);
        if(successI > 0){
            DocumentAuditInfo info = new DocumentAuditInfo();
            info.setAppId(appId);
            info.setApprover(appParams.getApprover());
            info.setComment(appParams.getComment());
            if("5".equals(status)){
                info.setStatus("4");
            }else{
                info.setStatus(status);
            }
            info.setNodeOrder(Integer.valueOf(appParams.getNodeOrder()));
            //info.setCreateBy(appParams.getApprover());
            info.setCreateTime(new Date());
            documentAuditInfoMapper.insertDocumentAuditInfo(info); // 审核记录
        }
        return successI;
    }

    @Override
    public List<JkApplicationResult> selectAppByUserId(String userId) {
        return jkApplicationMapper.selectAppByUserId(userId);
    }

    @Override
    public List<String> selectAppCollectList(String userId) {
        return appCollectMapper.selectAppCollectList(userId);
    }

    @Override
    public int insertAppCollect(AppCollect appCollect) {
        AppCollect collect = appCollectMapper.selectAppCollectDetail(appCollect.getAppId(), appCollect.getUserId());
        if (collect == null) {
            return appCollectMapper.insertAppCollect(appCollect);
        }
        return 0;
    }

    @Override
    public int deleteAppCollect(String userId, String appId) {
        return appCollectMapper.delAppCollect(userId, appId);
    }

    @Override
    public List<JkApplicationResult> selectRoleApp() {
        return jkApplicationMapper.selectRoleApp();
    }

    @Override
    public List<JkApplicationResult> getChatAppList(List<JkApplicationResult> params, String userId) {
        List<JkApplicationResult> list = new ArrayList();
        for (JkApplicationResult app : params) {
            AppCollect appCollect = appCollectMapper.selectAppCollectDetail(app.getAppId(), userId);
            SysDictData dictData = null;
            if(app.getTypeId() != null && !"".equals(app.getTypeId())){
                dictData = sysDictDataMapper.selectById(Long.valueOf(app.getTypeId()));
            }

            JkApplicationResult appResult = new JkApplicationResult();
            appResult.setAppId(app.getAppId());
            appResult.setName(app.getName());
            appResult.setDesc(app.getDesc());
            appResult.setTypeId(app.getTypeId());
            appResult.setCreateTime(app.getCreateTime());
            appResult.setChatMode("LOCAL");
            appResult.setIsCollect(appCollect != null ? true : false);
            appResult.setTypeName(dictData != null ? dictData.getDictLabel() : "");
            list.add(appResult);
        }
        return list;
    }

    private ApprovalNode getFirstApproverNode() {
        List<DocumentAuditConfig> list = documentAuditConfigMapper.selectDocumentAuditConfigListByBo(null);
        if(list != null && list.size() > 0) {
            ApprovalFlowManager flowManager = new ApprovalFlowManager();
            logger.info("开启审核");
            for (DocumentAuditConfig config : list) {
                // 添加审核节点
                flowManager.addNode(new ApprovalNode(config.getId(), config.getNodeOrder(), config.getApprover(), config.getChain()));
            }

            return flowManager.getFirstApprover();
        }
        return null;
    }

    public JSONObject analysisFile(List<UploadDocumentParams> result, JkDocumentSettings settings, String flag) {
        String url = deepseekConfig.getBaseurl() + deepseekConfig.getUploadKnowledge();
        List<JkKnowledge> knowledgeList = jkKnowledgeMapper.selectAllKnowledgeList(JkKnowledge.builder().knowledgeId(settings.getKnowledgeId()).build());
        JSONObject json = new JSONObject();
        json.put("knowledge_id", settings.getKnowledgeId());
        json.put("embedding_id", knowledgeList.get(0).getEmbeddingId());
        json.put("docs", result);
        json.put("userId",result.get(0).getUserId());
        json.put("tenantId",result.get(0).getTenantId());
        json.put("customSeparator",result.get(0).getCustomSeparator());
        json.put("parsingType",result.get(0).getParsingType());
        json.put("slice_value",result.get(0).getSliceValue());
        json.put("oldDocumentId",null != result.get(0).getOldDocumentId() ? result.get(0).getOldDocumentId() : null);
        json.put("oldKnowledgeId",null != result.get(0).getOldKnowledgeId() ? result.get(0).getOldKnowledgeId() : null);
        json.put("markDownFlg",null != result.get(0).getMarkDownFlg() ? result.get(0).getMarkDownFlg() : null);
        if(null != result.get(0).getRemark()) {
            json.put("remark",result.get(0).getRemark());
        }
        if(result.get(0).getStandardClassification().equals("2")) {
            json.put("excel_parsing",1);
        } else {
            json.put("excel_parsing",0);
        }

        if ("upload".equals(flag)) {
//            json.put("set_slice", "0");
//            json.put("slice_value", null);
//            json.put("set_analyze", "1");
//            json.put("set_table", "0");
            json.put("flag", "upload");
        } else {
//            json.put("set_slice", settings.getSetSlice());
//            json.put("slice_value", settings.getSliceValue());
//            json.put("set_analyze", settings.getSetAnalyze());
//            json.put("set_table", settings.getSetTable());
            json.put("flag", "update");
        }

        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), json.toJSONString());
        Request request = buildPostRequest(url, requestBody);
        OkHttpClient client = buildOkHttpClient();
        Response response = null;
        try {
            response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                String body = response.body().string();
                JSONObject obj = JSON.parseObject(body);
                Integer code = obj.getInteger("code");
                if (code == 200) {
                    logger.info("上传文档调用python接口成功,返回内容：{}", body);
                } else {
                    logger.info("上传文档调用python接口失败,返回内容：{}", body);
                }
                return obj;
            } else {
                logger.error("上传文档调用python接口失败返回code：{}", response.code());
                return null;
            }
        } catch (IOException e) {
            logger.error("上传文档调用python接口失败", e.getMessage());
            return null;
        }
    }

    @Override
    public List<Object> setDialogList(List<JkDialogRespDTO> list) {
        //根据时间分组降序排序
        NavigableMap<LocalDateTime, List<JkDialogRespDTO>> groupList = list.stream().collect(Collectors.groupingBy(JkDialogRespDTO::getCreate_time, TreeMap::new, Collectors.toList())).descendingMap();
        List<Object> obj = new ArrayList();
        if (!groupList.isEmpty()) {
            for (Map.Entry<LocalDateTime, List<JkDialogRespDTO>> entry : groupList.entrySet()) {
                List<JkDialogRespDTO> aList = entry.getValue();
                List<JkDialogRespDTO> detailList = jkDialogMapper.selectDialogExport(aList.get(0).getId());
                Map<String, Object> map = new HashMap();
                map.put("dialog_name", aList.get(0).getDialog_name());
                map.put("length", detailList.size());
                map.put("create_time", aList.get(0).getCreate_time());
                map.put("did", aList.get(0).getDid());
                map.put("id", aList.get(0).getId());
                obj.add(map);
            }
        }
        return obj;
    }

    @Override
    public Page<JkApplicationResult> getApplicationPageList(JkApplication params, PageQuery pageQuery) {
        //管理员 获取所有项目
        Map<String,Object> userParams = getUserParams();
        if(userParams != null){
            params.setParams(userParams);
        }
        Page<JkApplicationResult> appList = jkApplicationMapper.selectApplicationPageList(params,pageQuery.build());
        //添加应用VIP用户
//        if(appList != null && appList.size() > 0){
//            for (JkApplicationResult appResult : appList) {
//                List<SysUser> vipList = userService.selectAppVipList(appResult.getAppId());
//                appResult.setVipList(vipList);
//            }
//        }
        for(JkApplicationResult jkApplicationResult : appList.getRecords()) {
            List<AppNumList> numLists = jkApplicationMapper.selectSizeNumByApplicationId(jkApplicationResult.getAppId());
            // knowledgeId 级别的数量
            long knowledgeCount = numLists.stream()
                .map(AppNumList::getKnowledgeId)
                .distinct()
                .count();
            // documentId 级别的数量（按 knowledgeId 分组）
            long documentCount = numLists.stream()
                .map(AppNumList::getDocumentId)
                .distinct()
                .count();
            // sliceId 级别的数量（按 knowledgeId 和 documentId 分组）
            long sliceCount = numLists.stream()
                .map(AppNumList::getSliceId)
                .distinct()
                .count();
            jkApplicationResult.setKnowledgeSize(knowledgeCount);
            jkApplicationResult.setDocumentSize(documentCount);
            jkApplicationResult.setSliceSize(sliceCount);
        }
        return appList;
    }

    @Override
    public Page<JkApplicationResult> getMyApplicationPageList(JkApplication params, PageQuery pageQuery) {
        //管理员 获取所有项目
        Map<String,Object> userParams = getUserParams();
        if(userParams != null){
            params.setParams(userParams);
        }
        Page<JkApplicationResult> appList = jkApplicationMapper.selectMyApplicationPageList(params,pageQuery.build());
        //添加应用VIP用户
//        if(appList != null && appList.size() > 0){
//            for (JkApplicationResult appResult : appList) {
//                List<SysUser> vipList = userService.selectAppVipList(appResult.getAppId());
//                appResult.setVipList(vipList);
//            }
//        }
        for(JkApplicationResult jkApplicationResult : appList.getRecords()) {
            List<AppNumList> numLists = jkApplicationMapper.selectSizeNumByApplicationId(jkApplicationResult.getAppId());
            // knowledgeId 级别的数量
            long knowledgeCount = numLists.stream()
                .map(AppNumList::getKnowledgeId)
                .distinct()
                .count();
            // documentId 级别的数量（按 knowledgeId 分组）
            long documentCount = numLists.stream()
                .map(AppNumList::getDocumentId)
                .distinct()
                .count();
            // sliceId 级别的数量（按 knowledgeId 和 documentId 分组）
            long sliceCount = numLists.stream()
                .map(AppNumList::getSliceId)
                .distinct()
                .count();
            jkApplicationResult.setKnowledgeSize(knowledgeCount);
            jkApplicationResult.setDocumentSize(documentCount);
            jkApplicationResult.setSliceSize(sliceCount);
        }
        return appList;
    }

    private List<Map<String, Object>> setValue(List<JkDialogRespDTO> list, String appId) {
        List<Map<String, Object>> l = new ArrayList();
        for (JkDialogRespDTO vo : list) {
            Map<String, Object> map = new HashMap();
            map.put("key", vo.getId());
            map.put("label", vo.getDialog_name());
            map.put("appId", appId);
            map.put("showMenu", "false");
            map.put("chatMode", "LOCAL");
            l.add(map);
        }
        return l;
    }

    private OkHttpClient buildOkHttpClient() {
        return new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(50, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.MINUTES)
                .build();
    }

    private Request buildGetRequest(String url) {
        return new Request.Builder()
                .addHeader("User-Agent", "insomnia/10.3.1")
                .url(url).get().build();
    }

    private Request buildPostRequest(String url, RequestBody requestBody) {
        return new Request.Builder()
                .addHeader("User-Agent", "insomnia/10.3.1")
                .addHeader("Content-Type", "application/json")
                .url(url).post(requestBody).build();
    }

    private Request buildPutRequest(String url, RequestBody requestBody) {
        return new Request.Builder()
                .addHeader("User-Agent", "insomnia/10.3.1")
                .url(url).put(requestBody).build();
    }

    private Request buildDeleteRequest(String url) {
        return new Request.Builder()
                .addHeader("Content-Type", "multipart/form-data")
                .addHeader("User-Agent", "insomnia/10.3.1")
                .url(url).delete().build();
    }

    private Request buildUploadPostRequest(String url, RequestBody requestBody) {
        return new Request.Builder()
                .addHeader("User-Agent", "insomnia/10.3.1")
                .addHeader("Content-Type", "application/json")
                .url(url).post(requestBody).build();
    }

    private boolean isJsonObject(String data) {
        try {
            JSON.parseObject(data);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private void decodeMessage(List<JkPromptInfo> messages) {
        if(messages == null || messages.isEmpty()) {
            return;
        }
        for (JkPromptInfo message : messages) {
            message.setContent(base64DecodeUtil.decodeIfNeeded(message.getContent()));
        }
    }

    // 下载到内存
    private String downloadMdContent(String mdUrl) throws IOException {
        Request request = new Request.Builder().url(mdUrl).build();
        OkHttpClient client = buildOkHttpClient();
        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("下载失败：" + response);
            }
            return response.body().string();
        }
    }

}
