如何调用豆包大模型接口生成文章

公司项目中有个需求,要发布文章,方案中通过调用豆包大模型接口,生成文章,并展示在特定区域,还包括一键生成摘要、一键生成关键词等功能。

由于生成文章要采用流式输出的方式,故采用websocket的方式,前台页面发送问题给豆包大模型接口,大模型接口发送消息给前端,前端接收后台的流式输出返回信息后,展示在对应的div中。

首先,需要增加websocket的支持,在springboot项目新增WebSocketConfig类

@Configuration
@EnableWebSocket
public class WebSocketConfig  {

    @Bean
    public ServerEndpointExporter serverEndpoint() {
        return new ServerEndpointExporter();
    }

    @Bean
    public WebSocketContainer webSocketContainer() {
        WebSocketContainer webSocketContainer = ContainerProvider.getWebSocketContainer();
        webSocketContainer.setDefaultMaxSessionIdleTimeout(300000L);
        return webSocketContainer;
    }

}
然后新建一个websocket的服务,包含了onOpen、onMessage、onClose、OnError方法
package com.xxx.logistics.server;

import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.xxx.chainResource.DouBaoMessage;
import com.xxx.chainResource.IVolcengineDouBaoService;
import com.xxx.chainResource.server.ChatIMChannel;
import com.xxx.chainResource.server.DouBaoIMChannel;
import com.xxx.common.utils.EmptyUtil;
import com.xxx.common.utils.SensitiveUtils;
import io.swagger.annotations.Api;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;


@Log4j2
@Component
@Api(tags = "DouBaoIM 服务")
@ServerEndpoint(value = "/api/douBaoIM/server/{userId}")
public class DouBaoIMServer {


    private static IVolcengineDouBaoService volcengineDouBaoService;

    static {
        volcengineDouBaoService = SpringUtil.getBean(IVolcengineDouBaoService.class);
    }


    @OnOpen
    public void onOpen(@PathParam(value = "userId") String userId, Session session) throws IOException {
        if (EmptyUtil.isEmpty(ChatIMChannel.map.get(userId))) {
            DouBaoIMChannel.map.put(userId, new DouBaoIMChannel()
                    .setSession(session)
            );
        } else if (!ChatIMChannel.map.get(userId).session.isOpen()) {
            DouBaoIMChannel.map.get(userId).session.close();
            DouBaoIMChannel.map.remove(userId);
            DouBaoIMChannel.map.put(userId, new DouBaoIMChannel()
                    .setSession(session)
            );
        }

        log.info("WebSocket ——> DouBaoIM 服务端 连接 {}", userId);
    }

    @OnMessage
    public void onMessage(@PathParam(value = "userId") String userId, String message) throws IOException {
        
        DouBaoMessage douBaoMessage = JSONObject.parseObject(message, DouBaoMessage.class);
        if (EmptyUtil.isNotEmpty(userId) && EmptyUtil.isNotEmpty(douBaoMessage.getMessage())) {
            DouBaoIMChannel douBaoIMChannel = DouBaoIMChannel.map.get(userId);
            if (SensitiveUtils.getFilter().isSensitive(douBaoMessage.getMessage())) {
                if (EmptyUtil.isNotEmpty(douBaoIMChannel) && douBaoIMChannel.session.isOpen()) {
                  
                    //如果有敏感词,生成摘要和关键词时将敏感词替换成*进行提问
                    if(douBaoMessage.getCommand().equals("generateSummary")||douBaoMessage.getCommand().equals("generateKeywords")){
                        volcengineDouBaoService.aiDialogueStream(userId, SensitiveUtils.getFilter().filter(douBaoMessage.getMessage(),'*'), douBaoMessage.getCommand());
                    }else{
                        volcengineDouBaoService.aiDialogueStream(userId, douBaoMessage.getMessage(), douBaoMessage.getCommand());
                        douBaoIMChannel.sendMessage(
                                JSON.toJSONString(
                                        new DouBaoMessage().setMessage(message).setCommand("endOfAnswer")
                                )
                        );
                    }
                }
            } else {
                if (douBaoMessage.getCommand().equals("heartBeat")) {
                    if (EmptyUtil.isNotEmpty(douBaoIMChannel) && douBaoIMChannel.session.isOpen()) {
                        douBaoIMChannel.sendMessage(
                                JSON.toJSONString(
                                        new DouBaoMessage().setMessage(message).setCommand("heartBeat")
                                )
                        );
                    }
                } else if (douBaoMessage.getCommand().equals("generateArticle")) {
                    volcengineDouBaoService.aiDialogueStreamMultipleRounds(userId, douBaoMessage.getMessage(), douBaoMessage.getCommand(),douBaoMessage.getLoginUserId());
                    douBaoIMChannel.sendMessage(
                            JSON.toJSONString(
                                    new DouBaoMessage().setMessage(message).setCommand("endOfAnswer")
                            )
                    );
                } else {
                    volcengineDouBaoService.aiDialogueStream(userId, douBaoMessage.getMessage(), douBaoMessage.getCommand());
                    douBaoIMChannel.sendMessage(
                            JSON.toJSONString(
                                    new DouBaoMessage().setMessage(message).setCommand("endOfAnswer")
                            )
                    );
                }
            }
        }
        log.info("WebSocket ——> DouBaoIM 服务端 读取 {} {}", userId, message);
       
    }

    @OnClose
    public void onClose(@PathParam(value = "userId") String userId, Session session) {
        try {
            DouBaoIMChannel douBaoIMChannel = DouBaoIMChannel.map.get(userId);
            if (EmptyUtil.isNotEmpty(douBaoIMChannel) ) {
                if( EmptyUtil.isNotEmpty(douBaoIMChannel.session)) {
                    douBaoIMChannel.session.close();
                }
                DouBaoIMChannel.map.remove(userId);
            }
        } catch (Exception e) {
            log.error("WebSocket ——> DouBaoIM 服务端 断连 {}", e.getMessage());
        }
        log.info("WebSocket ——> ChatIM 服务端 断连 {}", userId);
    }

    @OnError
    public void OnError(@PathParam(value = "userId") String userId, Session session, Throwable throwable) {
        try {
            DouBaoIMChannel douBaoIMChannel = DouBaoIMChannel.map.get(userId);
            if (EmptyUtil.isNotEmpty(douBaoIMChannel) ) {
                if( EmptyUtil.isNotEmpty(douBaoIMChannel.session)) {
                    douBaoIMChannel.session.close();
                }
                DouBaoIMChannel.map.remove(userId);
            }
        } catch (Exception e) {
            log.error("WebSocket ——> DouBaoIM 服务端 错误 {}", e.getMessage());
        }
        // 记录原始异常及其堆栈跟踪
        log.error("WebSocket ——> DouBaoIM 服务端 错误 {}: {}", userId, throwable.getMessage());
    }


}

其中,每一个DouBaoIMChannel代表一个通道,里面有sendMessage方法用于websocket发送消息

package com.xxx.chainResource.server;

import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.log4j.Log4j2;

import javax.websocket.Session;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;


@Log4j2
@Data
@Accessors(chain = true)
public class DouBaoIMChannel {

    public static ConcurrentHashMap<String, DouBaoIMChannel> map = new ConcurrentHashMap<>();

    public Session session;

    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
        //log.info("WebSocket ——> DouBao 服务端 发送 {}",message);
    }

}

接下来,新建火山引擎 豆包 服务类IVolcengineDouBaoService 

package com.xxx.chainResource;

/**
 * 火山引擎 豆包 服务类
 *
 * @description
 */
public interface IVolcengineDouBaoService {

    /**
     * AI 对话
     * @param message
     * @return
     */
    String aiDialogue(String message);

    /**
     * AI 对话 流式
     * @param userId
     * @param message
     * @return
     */
    void aiDialogueStream(String userId,String message,String command);

    /**
     * AI 对话 流式 多轮次
     * @param userId
     * @param message
     * @param command
     */
    void aiDialogueStreamMultipleRounds(String userId, String message, String command,String loginUserId);

}

下面是IVolcengineDouBaoService 的实现类,aiDialogue采用一次性返回,aiDialogueStream采用流式输出通过websocket向客户端发送消息的方式返回,

aiDialogueStreamMultipleRounds方法也采用流式输出的方式返回,同时增加了连续提问的功能,当生成文章时,可以针对上一次的结果进行连续对话,当本次对话返回答案后,将结果存到数据库,并在下次提问时查询当天的该用户的最近5次问题和答案,同时发送给豆包接口,从而实现连续回话功能。

package com.xxx.chainResource.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.xxx.chainInfomation.QaPairs;
import com.xxx.chainInfomation.QaPairsService;
import com.xxx.chainInfomation.dto.QaPairsDTO;
import com.xxx.chainInfomation.qo.QaPairsQO;
import com.xxx.chainResource.DouBaoMessage;
import com.xxx.chainResource.IVolcengineDouBaoService;
import com.xxx.chainResource.server.DouBaoIMChannel;
import com.xxx.common.constant.VolcengineConstant;
import com.xxx.common.utils.EmptyUtil;
import com.volcengine.ark.runtime.model.completion.chat.ChatCompletionRequest;
import com.volcengine.ark.runtime.model.completion.chat.ChatCompletionResult;
import com.volcengine.ark.runtime.model.completion.chat.ChatMessage;
import com.volcengine.ark.runtime.model.completion.chat.ChatMessageRole;
import com.volcengine.ark.runtime.service.ArkService;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;

/**
 * 火山引擎 豆包 服务实现类
 * @description
 */
@Log4j2
@Service
public class VolcengineDouBaoServiceImpl implements IVolcengineDouBaoService {
    @Autowired
    private QaPairsService qaPairsService;
    @Override
    public String aiDialogue(String message) {
        StringBuffer result = new StringBuffer();
        ArkService service = new ArkService(VolcengineConstant.ARK_API_KEY);
        List<ChatMessage> messagesList = new ArrayList<>();
        messagesList.add(
                ChatMessage.builder().role(ChatMessageRole.SYSTEM)
                        .content(VolcengineConstant.INIT_SYSTEM_MESSAGE)
                        .build()
        );
        messagesList.add(
                ChatMessage.builder().role(ChatMessageRole.USER)
                        .content(message)
                        .build()
        );
        log.info("火山引擎 ——> DouBao 标准 {}",message);
        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
                .model(VolcengineConstant.MODEL_NODE)
                .messages(messagesList)
                .build();
        ChatCompletionResult chatCompletion = service.createChatCompletion(chatCompletionRequest);
        chatCompletion.getChoices().forEach(choice ->
                    result.append(choice.getMessage().getContent())
                );
        service.shutdownExecutor();
        return result.toString();
    }

    @Override
    public void aiDialogueStream(String userId,String message,String command) {
        ArkService service = new ArkService(VolcengineConstant.ARK_API_KEY);
        List<ChatMessage> messagesList = new ArrayList<>();
        messagesList.add(
                ChatMessage.builder().role(ChatMessageRole.SYSTEM)
                        .content(VolcengineConstant.INIT_SYSTEM_MESSAGE)
                        .build()
        );
        messagesList.add(
                ChatMessage.builder().role(ChatMessageRole.USER)
                        .content(message)
                        .build()
        );
        DouBaoIMChannel douBaoIMChannel = DouBaoIMChannel.map.get(userId);
        if(EmptyUtil.isNotEmpty(douBaoIMChannel) && douBaoIMChannel.session.isOpen()){
            log.info("火山引擎 ——> DouBao 流式 {}",message);
            ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
                    .model(VolcengineConstant.MODEL_NODE)
                    .messages(messagesList)
                    .build();
            service.streamChatCompletion(chatCompletionRequest)
                    .doOnError(Throwable::printStackTrace)
                    .blockingForEach(
                            choice -> {
                                if (EmptyUtil.isNotEmpty(choice.getChoices())) {
                                    douBaoIMChannel.sendMessage(
                                            JSON.toJSONString(
                                                    new DouBaoMessage().setMessage(String.valueOf(choice.getChoices().get(0).getMessage().getContent())).setCommand(command)
                                            )
                                    );
                                }
                            }
                    );
            service.shutdownExecutor();
        }
    }

    @Override
    public void aiDialogueStreamMultipleRounds(String userId , String message , String command,String loginUserId) {
        ArkService service = new ArkService(VolcengineConstant.ARK_API_KEY);
        List<ChatMessage> messagesList = new ArrayList<>();
        messagesList.add(
                ChatMessage.builder().role(ChatMessageRole.SYSTEM)
                        .content(VolcengineConstant.INIT_SYSTEM_MESSAGE)
                        .build()
        );
        List<QaPairs> douBaoQAPairsList = new ArrayList<QaPairs>();
        QaPairsQO qaPairsQO = new QaPairsQO();
        // 计算当天的开始和结束时间
        LocalDateTime startOfDay = LocalDateTime.now().with(LocalTime.MIN);
        LocalDateTime endOfDay = LocalDateTime.now().with(LocalTime.MAX);
        douBaoQAPairsList = qaPairsService.list(com.chenglian.chainCommon.Query.getPage(qaPairsQO.getCurrent(), qaPairsQO.getSize(), QaPairs.class),
                Wrappers.<QaPairs>lambdaQuery().eq(QaPairs::getUserId,loginUserId).between(QaPairs::getCreateTime, startOfDay, endOfDay).orderByDesc(QaPairs::getCreateTime)
        );

        if(EmptyUtil.isNotEmpty(douBaoQAPairsList)){
            for(QaPairs douBaoQAPairs:douBaoQAPairsList){
                messagesList.add(
                        ChatMessage.builder().role(ChatMessageRole.USER)
                                .content(douBaoQAPairs.getQuestion())
                                .build()
                );
                messagesList.add(
                        ChatMessage.builder().role(ChatMessageRole.ASSISTANT)
                                .content(douBaoQAPairs.getAnswer())
                                .build()
                );
            }
        }
        messagesList.add(
                ChatMessage.builder().role(ChatMessageRole.USER)
                        .content(message)
                        .build()
        );
        DouBaoIMChannel douBaoIMChannel = DouBaoIMChannel.map.get(userId);
        StringBuffer result = new StringBuffer();
        if(EmptyUtil.isNotEmpty(douBaoIMChannel) && douBaoIMChannel.session.isOpen()){
            log.info("火山引擎 ——> DouBao 流式多轮次 {}",message);
            ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
                    .model(VolcengineConstant.MODEL_NODE)
                    .messages(messagesList)
                    .build();
            service.streamChatCompletion(chatCompletionRequest)
                    .doOnError(Throwable::printStackTrace)
                    .blockingForEach(
                            choice -> {
                                if (EmptyUtil.isNotEmpty(choice.getChoices())) {
                                    douBaoIMChannel.sendMessage(
                                            com.alibaba.fastjson.JSON.toJSONString(
                                                    new DouBaoMessage().setMessage(String.valueOf(choice.getChoices().get(0).getMessage().getContent())).setCommand(command)
                                            )
                                    );
                                    result.append(String.valueOf(choice.getChoices().get(0).getMessage().getContent()));
                                }
                            }
                    );
            recordQAHistory(loginUserId,message,result.toString());
        }
    }

    @Async("myTaskAsyncPool")
    public void recordQAHistory(String loginUserId, String message,String result ) {
        QaPairsDTO qaPairsDTO = new QaPairsDTO();
        qaPairsDTO.setUserId(loginUserId).setQuestion(message).setAnswer(result).setCreateTime(LocalDateTime.now());
        qaPairsService.submit(qaPairsDTO);
    }

}

VolcengineConstant.ARK_API_KEY是大模型的鉴权 API key

VolcengineConstant.MODEL_NODE是大模型的模型节点,这两个是需要付费获取的,本实例就不在这里具体展示了。这样后端程序的实现就完成啦。下面是前端实现,同时实现了心跳发送startHeartbeat,断线重连reconnectWebSocket,通过sendCommand发送指令给大模型接口,同时ws.onmessage方法接受后台返回的消息,并根据不同的类型展示在前端的不同div中。

var ws;
var heartbeatInterval = null;

function connectWebSocket() {
    var timestamp = new Date().getTime();
     //生成一个随机数
    var randomNum = Math.floor(Math.random() * 900 + 100)
     //将时间戳和随机数拼接成一个字符串
    var userId = `unique-${timestamp}-${randomNum}`;

    if (ws && ws.readyState !== WebSocket.CLOSED) {
        console.log('WebSocket already connected or connecting...');
        return;
    }

    var url = websocketServerUrl+`/api/douBaoIM/server/${userId}`;
   
    ws = new WebSocket(url);
   
    ws.onopen = function() {
        console.log('WebSocket Connected');
        startHeartbeat();
    };

    ws.onmessage = function(event) {
        //console.log(event)
        var message = JSON.parse(event.data);
        if (message.command === 'generateSummary') {
            displaySummaryMessage(message.message);
        } else if (message.command === 'generateKeywords') {
            displayKeywordsMessage(message.message);
        }else if (message.command === 'generateThreeQuestions') {
            displayThreeQuestionsStream(message.message);
        }else if (message.command === 'generateArticle') {
            displayArticle(message.message);
        }else if (message.command === 'generateAnswerToTheQuestion') {
            displayAnswerToTheQuestion(message.message);
        } else if (message.command === 'dontKnow') {
            layer.msg(message.message)
        }else if (message.command === 'heartBeat') {
            console.log(message.message);
        }else if(message.command === 'endOfAnswer'){
            setButtonEnabled();
        }
    };

    ws.onerror = function(error) {
        layer.msg("连接似乎断了,正在重连,请稍后重新发送问题")
        console.error('WebSocket Error: ', error);
    };

    ws.onclose = function() {
        layer.msg("连接似乎断了,正在重连,请稍后重新发送问题")
        console.log('WebSocket Connection Closed');
        if (heartbeatInterval) {
            clearInterval(heartbeatInterval);
            heartbeatInterval = null;
        }
        reconnectWebSocket();
    };
}

/**
 * 开启心跳
 */
function startHeartbeat() {
    if (heartbeatInterval) {
        clearInterval(heartbeatInterval);
    }
    heartbeatInterval = setInterval(() => {
        if (ws && ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({ command: 'heartBeat',message: '心跳' }));
        }
    }, 10000); // 假设每10秒发送一次心跳
}

/**
 * 重连
 */
function reconnectWebSocket() {
    console.log('Attempting to reconnect WebSocket...');
    setTimeout(() => {
        connectWebSocket();
    }, 5000);
}

/**
 * 发送指令
 * @param command
 * @param message
 */
function sendCommand(command, message,loginUserId) {
    if (ws && ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify({ command: command, message: message, loginUserId:loginUserId }));
    } else {
        console.error('WebSocket is not open');
        layer.msg("连接似乎断了,正在重连,请稍后重新发送问题");
        reconnectWebSocket();
    }
}

/**
 * 一键生成摘要
 */
function generateSummary() {
    $("#nvcAbstract").val('');
    var input = document.getElementById('nvcContent');
    var message = input.value.trim();
    message = removeImgTags(message);
    if(message==''){
        layer.msg("文章内容为空,无法生成摘要")
        $("#nvcContent").focus();
        return false;
    }
    message = message+",请为上面的文章生成最多120个字的摘要,确保摘要长度严格满足不能超过120个字,且不要包含任何非摘要内容,如‘**摘要**:’标记。";
    sendCommand('generateSummary', message,loginUserId);
}

/**
 * 一键生成关键词
 */
function generateKeywords() {
    $("#nvcKeywords").val('');
    var input = document.getElementById('nvcContent');
    var message = input.value.trim();
    message = removeImgTags(message);
    if(message==''){
        layer.msg("文章内容为空,无法生成关键词")
        $("#nvcContent").focus();
        return false;
    }
    message = message+",请为上面的文章生成最多5个关键词,关键词之间用中文逗号“,”分隔,关键词中不要有隐藏符";
    sendCommand('generateKeywords', message,loginUserId);
}

/**
 * 去除<img>标签
 * @param htmlContent
 * @returns {string}
 */
function removeImgTags(htmlContent) {
    // 使用DOMParser解析HTML字符串
    const parser = new DOMParser();
    const doc = parser.parseFromString(htmlContent, 'text/html');

    // 获取所有的<img>元素
    const imgs = doc.querySelectorAll('img');

    // 遍历所有<img>元素并移除它们
    imgs.forEach(img => img.remove());

    // 将修改后的HTML转换回字符串
    return doc.body.innerHTML;
}

/**
 * 生成三个问题
 */
var isGeneratingQuestions = false;
function generateThreeQuestions(){
    if (isGeneratingQuestions) {
        return; // 如果已经在生成问题,则直接返回
    }
    isGeneratingQuestions = true; // 设置标志位为 true
    var inspirationInspires = $("#inspirationInspires").val();
    var message = inspirationInspires.trim();
    if(message==''){
        layer.tips("请输入内容","#inspirationInspires");
        $("#inspirationInspires").focus();
        isGeneratingQuestions = false; // 恢复标志位
        return false;
    }
    message = message+",请根据上面的内容提出3个问题";
    sendCommand('generateThreeQuestions', message,loginUserId);
    $("#changeBatchLink").hide();
    $('#threeQuestions .doub-box-ques').remove();
    $('#inspirationInspiresSpan').removeClass('fosng cursor').addClass('fosnghui');
    $("#loadingEffect").show();
}

/**
 * 生成另外三个问题
 */
function generateOtherThreeQuestions(){
    var inspirationInspires = $("#inspirationInspires").val();
    var message = inspirationInspires.trim();
    if(message==''){
        layer.tips("请输入内容","#inspirationInspires");
        $("#inspirationInspires").focus();
        return false;
    }
    message = message+",请根据上面的内容提出与之前不同的3个问题";
    sendCommand('generateThreeQuestions', message,loginUserId);
    $("#changeBatchLink").hide();
    $('#threeQuestions .doub-box-ques').remove();
    $("#loadingEffect").show();
}

/**
 * 生成文章
 */
var isGeneratingArticle = false;
function generateArticle(){
    if (isGeneratingArticle) {
        return; // 如果已经在生成问题,则直接返回
    }
    isGeneratingArticle = true;
    var helpMeWrite = $("#helpMeWrite").val();
    var message = helpMeWrite.trim();
    if(message==''){
        layer.tips("请输入内容","#helpMeWrite");
        $("#helpMeWrite").focus();
        isGeneratingArticle = false;
        return false;
    }
    sendCommand('generateArticle', message,loginUserId);
    $('#articleContent').empty();
    $('#helpMeWriteSpan').removeClass('fosng cursor').addClass('fosnghui');
    $('#articleContent').append('<div id="loadingIndicator" style="display:none;"><i class="iconfont icon-loading"></i> 正在生成文章...</div>');
    $("#loadingIndicator").show();
}

/**
 * 生成问题的答案
 */
function generateAnswerToTheQuestion(message){
    sendCommand('generateAnswerToTheQuestion', message,loginUserId);
    updateQuestion(message);
    $('#answerToTheQuestion').empty();
}

/**
 * 显示摘要
 * @param message
 */
function displaySummaryMessage(message) {
    var nvcAbstractElement = $("#nvcAbstract"); // 获取文本域元素
    var nvcAbstract = nvcAbstractElement.val(); // 获取当前文本域的内容
    var newContentLength = nvcAbstract.length + message.length; // 计算新内容的长度
    // 检查新内容的长度是否超过120字
    if (newContentLength <= 120) {
        // 如果未超过,则添加消息并更新文本域
        nvcAbstract += message;
        nvcAbstractElement.val(nvcAbstract);
        changeSize(); // 假设这个函数用于调整某些样式或布局
    } else {
        // 如果超过,可以选择不执行任何操作,或者给用户一些反馈
        console.log("摘要已超过120字限制,无法添加更多内容。");
        // 这里也可以考虑截断当前内容到120字,或者显示一个警告消息给用户
    }
}

/**
 * 显示关键词
 * @param message
 */
function displayKeywordsMessage(message) {
    var nvcKeywords = $("#nvcKeywords").val();
    nvcKeywords = nvcKeywords+message;
    $("#nvcKeywords").val(nvcKeywords);
}

/**
 * 显示文章
 * @param message
 */
function displayArticle(message) {
    // 获取目标div元素
    var articleContentDiv = document.getElementById('articleContent');

    // 确保获取到了元素
    if (articleContentDiv) {
        $("#loadingIndicator").hide();
        var currentContent = articleContentDiv.innerHTML;
        // 使用innerHTML来设置新内容,将\n替换为<br>以实现换行,并保留当前内容
        articleContentDiv.innerHTML = currentContent + message.replace(/\n/g, '<br>');
        var dialogContent = $('.layui-layer-content');
        dialogContent.scrollTop(dialogContent[0].scrollHeight);
    } else {
        // 如果没有找到元素,则在控制台输出错误信息
        console.error('Element with ID "articleContent" not found.');
    }
}

/**
 * 显示问题答案
 * @param message
 */
function displayAnswerToTheQuestion(message) {
    // 获取目标div元素
    var answerToTheQuestionDiv = document.getElementById('answerToTheQuestion');
    // 确保获取到了元素
    if (answerToTheQuestionDiv) {
        // 获取当前div的内容
        var currentContent = answerToTheQuestionDiv.innerHTML;
        // 使用innerHTML来设置新内容,将\n替换为<br>以实现换行,并保留当前内容
        answerToTheQuestionDiv.innerHTML = currentContent + message.replace(/\n/g, '<br>');
        var dialogContent = $('.layui-layer-content');
        dialogContent.scrollTop(dialogContent[0].scrollHeight);
    } else {
        // 如果没有找到元素,则在控制台输出错误信息
        console.error('Element with ID "articleContent" not found.');
    }
}

//更改字数限制
function changeSize() {
    var text = $("#nvcAbstract").val();
    $("#kewordNum").html(text.length)
}
//设置按钮成为可用状态
function setButtonEnabled() {
    isGeneratingQuestions = false;
    isGeneratingArticle = false;
    $('#inspirationInspiresSpan').removeClass('fosnghui').addClass('fosng cursor');
    $('#helpMeWriteSpan').removeClass('fosnghui').addClass('fosng cursor');

    toggleLink(true);
}

function toggleLink(enable) {
    var link = document.querySelector('a.back-btn');
    if (enable) {
        link.classList.remove('disabled');
        link.href = "javascript:hideQd();"; // 或者是你想要链接到的URL
        link.style.pointerEvents = 'auto'; // 重新启用鼠标事件
    } else {
        link.classList.add('disabled');
        link.href = "javascript:void(0);"; // 阻止任何默认行为
        link.style.pointerEvents = 'none'; // 禁用鼠标事件
    }
}

/**
 * 一次性返回三个问题展示
 * @param messageFragment
 */
function displayThreeQuestions(messageFragment) {
    var questionsArray =[];

    // 如果需要忽略引导语,并且缓冲区中还没有换行符
    if (ignoreIntro && messageFragment.indexOf('\n') === -1) {
        // 那么我们什么也不做,等待更多的消息片段
        return;
    }

    // 查找换行符以分割问题
    questionsArray = messageFragment.split('\n').filter(question => question.trim() !== '');

    // 移除数组中的第一个元素(可能是空字符串或引导语)
    questionsArray.shift();

    // 检查是否还有至少三个问题剩余
    if (questionsArray.length > 3) {
        questionsArray = questionsArray.slice(0, 3); // 只保留后三个问题
    }

    // 遍历并处理保留的问题
    questionsArray.forEach((question, index) => {
        // 去除问题字符串前后的空格
        question = question.trim();

        // 如果问题字符串中包含'.',则只取'.'之后的内容
        if (question.indexOf('.') !== -1) {
            question = question.substring(question.indexOf('.') + 1).trim();
        }

        // 如果问题不为空,则添加到DOM中
        if (question) {
            // 注意:这里我们假设addToDOM函数能够正确处理编号,这里直接使用index+1作为编号
            addToDOM(question, index + 1);
        }
    });
}

/**
 * 将问题添加到div
 * @param question
 * @param number
 */
function addToDOM(question, number) {
    $("#loadingEffect").hide();
    var questionsContainer = document.getElementById('threeQuestions');

    // 创建一个新的span来同时包含编号和问题
    var combinedSpan = document.createElement('span');

    // 创建编号的文本节点
    var numberText = document.createTextNode(`问题${number}:`);

    // 创建问题的a标签
    var questionLink = document.createElement('a');
    questionLink.href = 'javascript:void(0);'; // 阻止默认行为
    questionLink.textContent = question;
    // 这里可以添加点击事件的处理逻辑
    // 为a标签添加点击事件监听器
    questionLink.addEventListener('click', function() {
        showQd();
        generateAnswerToTheQuestion(questionLink.textContent);
    });
    // 将编号和问题的a标签添加到combinedSpan中
    combinedSpan.appendChild(numberText);
    combinedSpan.appendChild(document.createTextNode(' ')); // 在编号和问题之间添加空格
    combinedSpan.appendChild(questionLink);

    // 创建一个新的div来包含span
    var questionDiv = document.createElement('div');
    questionDiv.className = 'doub-box-ques';

    // 将combinedSpan添加到div中
    questionDiv.appendChild(combinedSpan);

    // 将div添加到页面上的容器中
    questionsContainer.appendChild(questionDiv);
    if(number==3){
        $("#changeBatchLink").show();
    }
}

var questionBuffer = ''; // 当前正在构建的问题的缓冲区
var numberBuffer = '';   // 用于临时存储编号的整数部分的缓冲区
var inQuestion = false;  // 标记是否正在处理一个问题
var nextQuestionNumber = 1; // 下一个问题的预期编号(主要用于调试和验证)
/**
 * 按流式结果返回三个问题展示
 * @param messageFragment
 */
function displayThreeQuestionsStream(messageFragment) {

    if(nextQuestionNumber==4){
        nextQuestionNumber = 1;
    }
    // 移除消息片段前后的空白字符
    var trimmedFragment = messageFragment;

    // 处理编号的整数部分
    if (/^\d+$/.test(trimmedFragment)) {
        numberBuffer = trimmedFragment; // 存储编号的整数部分
        // 注意:此时我们还没有接收到点,所以还不能确定这是一个新问题的开始
    } else if (trimmedFragment === ".") {
        // 如果之前存储了编号的整数部分,并且当前片段是点,则开始收集问题描述
        if (numberBuffer) {
            inQuestion = true; // 标记开始处理新问题
            questionBuffer = `${numberBuffer}.`; // 将编号(包括点)添加到问题缓冲区
            numberBuffer = ''; // 重置编号整数部分的缓冲区
        }
        // 否则,如果单独接收到点而没有之前的编号整数部分,则忽略它
    } else if (inQuestion) {
        // 如果正在处理一个问题,并且接收到了非空且非换行的消息片段
        if (trimmedFragment !== '' && trimmedFragment !== '\n') {
            questionBuffer += trimmedFragment; // 将片段添加到问题缓冲区
        } else if (trimmedFragment === '\n'||trimmedFragment === '') {
            // 如果接收到了换行符或者“”,并且正在处理一个问题,则处理该问题
            if (questionBuffer.trim() !== '') {
                // 如果问题字符串中包含'.',则只取'.'之后的内容
                if (questionBuffer.trim().indexOf('.') !== -1) {
                    questionBuffer = questionBuffer.trim().substring(questionBuffer.trim().indexOf('.') + 1).trim();
                }
                addToDOM(questionBuffer.trim(), nextQuestionNumber++);
                questionBuffer = ''; // 重置问题缓冲区
                inQuestion = false; // 标记问题处理完成
            }
        }
    }
}

/**
 * 更新问题
 * @param newContent
 */
function updateQuestion(newContent) {
    // 获取div元素
    var questionDiv = document.getElementById("theQuestion");

    // 创建一个新的文本节点,包含传入的新内容
    var newTextNode = document.createTextNode(newContent);

    // 获取“返回问题”链接元素,以便稍后可以将其重新添加到div中
    var backButton = questionDiv.querySelector(".back-btn");

    // 清空div内的所有现有内容
    questionDiv.innerHTML = "";

    // 将新内容添加到div中
    questionDiv.appendChild(newTextNode);

    // 将“返回问题”链接重新添加到div中
    questionDiv.appendChild(backButton);

    toggleLink(false);
}

$(document).ready(function() {
    $(document).keydown(function(event) {
        // 检查触发事件的元素是否为#myInput
        if (event.target.id === "inspirationInspires") {
            // 检查按键是否为回车键
            if (event.which === 13) {
                event.preventDefault(); // 阻止表单提交(如果input位于form中)
                // 这里执行你希望在回车时触发的代码
                generateThreeQuestions();
            }
        }
        if (event.target.id === "helpMeWrite") {
            // 检查按键是否为回车键
            if (event.which === 13) {
                event.preventDefault(); // 阻止表单提交(如果input位于form中)
                // 这里执行你希望在回车时触发的代码
                generateArticle();
            }
        }
    });
});

connectWebSocket();

下面是html展示,展示了生成问题,更换问题,帮我写作等功能

<div id="doub-box" style="display: none;">
        <div class="doub-box">
            <div class="doub-box-hd">
                <ul>
                    <li class="on" onclick="doubxx(1,this)">灵感启发</li>
                    <li onclick="doubxx(2,this)">帮我写作</li>
                </ul>
            </div>
            <div class="mt20" id="doub1">
                <div class="doub-box-tips">
                    1. 我们可以根据您感兴趣的内容,为您推荐话题,从而帮助你提升写作灵感; <br>2. 同时你也可以选择我们推荐的话题,我们将围绕相关话题帮您写作文章。
                </div>
                <div class="doub-box-textarea mt20">
                    <input id="inspirationInspires" placeholder="可以是一个或多个关键字,也可以是简要的一句话!" maxlength="50" autocomplete="off"></input>
                    <div class="flex-end"><span id="inspirationInspiresWordNum">0</span>/50
                        <span id="inspirationInspiresSpan" class="fosng cursor" onclick="generateThreeQuestions()"><i class="iconfont icon-emizhifeiji"></i></span>
                    </div>
                </div>
                <div class="mt10" id="threeQuestions">
                    <div id="loadingEffect" style="display:none;">
                        <i class="iconfont icon-loading"></i> 正在生成问题...
                    </div>
                    <a href="javascript:generateOtherThreeQuestions();" id="changeBatchLink" class="y_blue" style="display: none"><i class="iconfont icon-shuaxin1"></i>&nbsp;换一批</a>
<!--                    <div class="doub-box-ques">
                        <span>问题1:
                        <a href="javascript:showQd();">运输距离长:可能涉及跨区域甚至跨国的长途运输。 比如,从澳大利亚进口的铁矿石怎么运输到中国?</a></span></div>
                    <div class="doub-box-ques">
                        <span>问题2:
                        <a  href="javascript:showQd();">运输距离长:可能涉及跨区域甚至跨国的长途运输。 比如,从澳大利亚进口的铁矿石怎么运输到中国?</a></span>
                    </div>
                    <div class="doub-box-ques">
                        <span>问题3:
                        <a  href="javascript:showQd();">运输距离长:可能涉及跨区域甚至跨国的长途运输。 比如,从澳大利亚进口的铁矿石怎么运输到中国?</a></span>
                    </div>-->
                </div>
            </div>
            <div class="mt20" id="doub2" style="display: none;">
                <div class="doub-box-tips">我们可以帮助你写作文章,比如你可以这样跟我互动,eg“帮我写一篇分析网络货运平台优势的文章”</div>
                <div class="doub-box-textarea mt20">
                    <input id="helpMeWrite" placeholder="请告诉我,你要写作的内容吧!" maxlength="50" autocomplete="off"></input>
                    <div class="flex-end"><span id="helpMeWriteWordNum">0</span>/50
                        <!--<a href="javascript:generateArticle();" class="fosng"><i class="iconfont icon-emizhifeiji"></i></a>-->
                        <span id="helpMeWriteSpan" class="fosng cursor" onclick="generateArticle()"><i class="iconfont icon-emizhifeiji"></i></span>
                    </div>
                </div>
                <div id="articleContent" class="mt20 font14 lineheight2">
                    <div id="loadingIndicator" style="display:none;">
                        <i class="iconfont icon-loading"></i> 正在生成文章...
                    </div>
                    <!--以下是一些提高大宗物流运输效率的方法: 优化运输路线规划: 使用先进的物流软件和数据分析,考虑道路状况、交通流量、运输距离等因素,规划出最优化的运输路线。例如,通过实时交通数据,避免拥堵路段,减少运输时间。 选择合适的运输方式: 根据货物特点、运输距离和成本等因素,综合选择铁路、公路、水路或多式联运。比如,对于长途运输且时效性要求不高的大宗商品,优先选择铁路或水路运输;对于短途且时效性要求高的,可以选择公路运输。-->
                </div>
            </div>
            <div class="mt20" id="doub3" style="display: none;">
                <div id="theQuestion" class="doub-box-tips flex-between">运输距离长:可能涉及跨区域甚至跨国的长途运输。 比如,从澳大利亚进口的铁矿石怎么运输到中国?
                    <a href="javascript:hideQd();" class="back-btn">返回问题</a></div>
                <div id="answerToTheQuestion" class="mt20 font14 lineheight2">
                    <!--以下是一些提高大宗物流运输效率的方法: 优化运输路线规划: 使用先进的物流软件和数据分析,考虑道路状况、交通流量、运输距离等因素,规划出最优化的运输路线。例如,通过实时交通数据,避免拥堵路段,减少运输时间。 选择合适的运输方式: 根据货物特点、运输距离和成本等因素,综合选择铁路、公路、水路或多式联运。比如,对于长途运输且时效性要求不高的大宗商品,优先选择铁路或水路运输;对于短途且时效性要求高的,可以选择公路运输。-->
                </div>
            </div>
        </div>
    </div>

好啦,这个就是通过网页调用大模型接口,并结合websocket,实现接入ai功能的整体实现了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值