前端对接fastGPT流式数据+打字机效果

首先在对接api时 参数要设置stream: true,

      const data = {
        chatId: 'abc',
        stream: true,//这里true返回流式数据
        detail: false,
        variables: {
          uid: 'sfdsdf',
          name: 'zhaoyunyao,'
        },
        messages: [{ content: text, role: 'user' }]
      };

不要用axios发请求 不然处理不了流式数据 我这里使用fetch

        const response = await fetch(`${url}`, {
          method: 'post',
          headers: headers,
          body: JSON.stringify(data)
        });
        const reader = response.body.getReader();
        //创建了一个读取器对象,用于从响应主体中读取数据。response.body 是一个 ReadableStream 对象,通过调用 getReader() 方法可以获取一个读取器对象,以便逐步读取响应的内容。
        // 循环读取响应流
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          // 将ArrayBuffer转为文本
          const chunk = new TextDecoder('utf-8').decode(value);
          // 处理文本为json格式
          const jsonArr = chunk.trim().replace(/\n/g, '').split('data: ').splice(1)
          for (let index = 0; index < jsonArr.length; index++) {
            const json = jsonArr[index];
            try {
              if (JSON.parse(json).choices) {
                const text = JSON.parse(json).choices[0].delta.content ?? ''
                content += text.replace(/^\n/g, '')
              } else {
                content = "内部出了问题o(╥﹏╥)o"
              }
            } catch {
              // 处理转json不报错
            }
          }
          obj.content = content //这里的content就是最终输出的文本
        }

然后我们再加一个打字机的光标 用html+css实现

              <div class="chat-item-details">
                {{ item.content }}
                /** 这里的span就是光标 **/
                <span class="cursor-blink" v-show="item.awaitReply">  </span>
              </div>

再写上对应的css 

              .cursor-blink {
                display: inline-block;
                height: 16px;
                margin-bottom: -3px;
                width: 2px;
                animation: blink 1s infinite steps(1, start);
              }

              /*这里设置动画blink*/
              @keyframes blink {

                0%,
                100% {
                  background-color: #000;
                  color: #aaa;
                }

                50% {
                  background-color: #bbb;
                  /* not #aaa because it's seem there is Google Chrome bug */
                  color: #000;
                }
              }

最后呈现的效果

上图呈现的差不多是打字机的效果了 不过呢 但在传输过程中每次停顿后会跳出一串内容然后又停顿一会,阅读体验有些不流畅, 就像玩游戏时帧数低卡顿的感觉, 我们用一个队列让它逐字地展示出来,并且根据传输速度控制输出的速度

  1. 需要一个打字机队列
  2. 队列提供入队和消费功能
  3. 需要一个动态时间来控制文字输出

 

    // 打字机队列
    // 添加队列
    addQueue(str,obj) {
      obj.queue.push(...str.split(''))
    },
    // 消费队列
    consume(obj) {
      if (obj.queue.length > 0) {
        let str = obj.queue.shift()
        str && this.onConsume(str,obj)
      } else if (obj.isDone) {
        obj.consuming = false
        clearTimeout(obj.timmer)
        obj.awaitReply = false
        this.scrollBottom()
      }
    },
    // 消费间隔
    time(obj) {
      let time = 1000 / obj.queue.length
      return time > 100 ? 100 : time
    },
    // 消费下一个
    next(obj) {
      this.consume(obj)
      obj.timmer = setTimeout(() => {
        if (obj.consuming) {
          this.next(obj)
        }
      }, this.time(obj))
    },
    start(obj) {
      obj.consuming = true
      obj.isDone=false
      this.next(obj)
    },
    done(obj) {
      obj.isDone=true
    },
    onConsume(str,obj) {
      obj.content += str
    },

 加了过后的效果

最后附上完整代码

export default {
  data() {
    return {
      key: "xxx",
      AppId: "xx",
      text: "",
      readonly: false,
      messages: [
        { content: "您好,我是小环!请问需要什么帮助呢?", role: 'assistant', awaitReply: false },
      ],
      userImg: this.$store.getters.avatar,
      username: this.$store.getters.nickname,
      awaitReply: false,
      timmer: null,
      obj: null,
      queue: [],
      consuming: false,
      isDone: false
    }
  },
  mounted() {
    const messageTextarea = document.getElementById('messageTextarea');
    messageTextarea.addEventListener('keydown', (event) => {
      // 如果按下的是回车键(Enter)
      if (event.key === 'Enter' && !event.ctrlKey) {
        event.preventDefault(); // 阻止默认的换行行为
        // 在这里可以添加发送消息的逻辑
        this.send();
      } else if (event.key === 'Enter' && event.ctrlKey) {
        const cursorPosition = messageTextarea.selectionStart; // 获取光标位置
        const textBeforeCursor = messageTextarea.value.substring(0, cursorPosition); // 获取光标前的文本
        const textAfterCursor = messageTextarea.value.substring(cursorPosition); // 获取光标后的文本
        messageTextarea.value = textBeforeCursor + '\n' + textAfterCursor; // 在光标位置插入换行符
        messageTextarea.selectionStart = cursorPosition + 1; // 设置光标位置为插入换行符后的位置
        messageTextarea.selectionEnd = cursorPosition + 1;
      }
    });
  },
  methods: {
    // 打字机队列
    // 添加队列
    addQueue(str, obj) {
      obj.queue.push(...str.split(''))
    },
    // 消费队列
    consume(obj) {
      if (obj.queue.length > 0) {
        let str = obj.queue.shift()
        str && this.onConsume(str, obj)
      } else if (obj.isDone) {
        obj.consuming = false
        clearTimeout(obj.timmer)
        obj.awaitReply = false
        this.scrollBottom()
      }
    },
    // 消费间隔
    time(obj) {
      let time = 500 / obj.queue.length
      return time > 50 ? 50 : time
    },
    // 消费下一个
    next(obj) {
      this.consume(obj)
      obj.timmer = setTimeout(() => {
        if (obj.consuming) {
          this.next(obj)
        }
      }, this.time(obj))
    },
    start(obj) {
      obj.consuming = true
      obj.isDone = false
      this.next(obj)
    },
    done(obj) {
      obj.isDone = true
    },
    onConsume(str, obj) {
      obj.content += str
    },
    async send() {
      if (this.text === "" || /^\s+$/.test(this.text)) {
        this.$message.warning('请输入内容')
        return
      }
      const text = this.text
      this.text = ""
      const url = 'https://api.fastgpt.in/api/v1/chat/completions';
      this.messages.push({ role: 'user', content: text });
      let obj = { content: "", role: 'assistant', awaitReply: true, queue: [], consuming: false, isDone: false, timmer: null }
      this.messages.push(obj);
      this.scrollBottom()
      const data = {
        // 这里可以设置请求参数
        chatId: 'abc',
        stream: true,
        detail: false,
        variables: {
          uid: 'sfdsdf',
          name: 'zhaoyunyao,'
        },
        messages: [{ content: text, role: 'user' }]
      };
      const headers = {
        // 这里可以设置请求头
        Authorization: `Bearer ${this.key}`,
        "Content-Type": "application/json"
      };
      try {
        const response = await fetch(`${url}`, {
          method: 'post',
          headers: headers,
          body: JSON.stringify(data)
        });
        const reader = response.body.getReader();
        //let content = ""
        // 开始打字机队列
        this.start(obj)
        // 循环读取响应流
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          // 将ArrayBuffer转为文本
          const chunk = new TextDecoder('utf-8').decode(value);
          // 处理文本为json格式
          const jsonArr = chunk.trim().replace(/\n/g, '').split('data: ').splice(1)
          for (let index = 0; index < jsonArr.length; index++) {
            const json = jsonArr[index];
            try {
              if (JSON.parse(json).choices) {
                const text = JSON.parse(json).choices[0].delta.content ?? ''
                this.addQueue(text.replace(/^\n/g, ''), obj)
              } else {
                this.addQueue('内部出了问题o(╥﹏╥)o', obj)
              }
            } catch {
              // 处理转json不报错
            }
          }
          this.scrollBottom()
        }
      } catch (error) {
        console.error('请求错误:', error);
      }
      this.done(obj)
    },
    // 滚到最底部
    scrollBottom() {
      setTimeout(() => {
        const mainChat = this.$refs.mainChat
        mainChat.scrollTop = mainChat.scrollHeight
      }, 0)
    },
  }
}

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,以下是一个简单的 Java 后端和前端对接数据的案例,使用的是 Spring Boot 框架。 1. 后端代码 在后端,我们需要定义一个 RestController,用于处理前端发送的 HTTP 请求,返回 JSON 格式的数据。这里我们实现一个获取用户信息的接口,代码如下: ```java @RestController public class UserController { @Autowired private UserService userService; @GetMapping("/users/{id}") public User getUser(@PathVariable Long id) { return userService.getUserById(id); } } ``` 在上面的代码中,我们使用 @RestController 注解标记一个控制器,指定了请求的 URL 为 /users/{id},其中 {id} 是一个占位符,表示用户的 ID。在方法中,我们使用 @GetMapping 注解标记了一个 GET 请求,返回了一个 User 对象,它会自动转换为 JSON 格式返回给前端。 2. 前端代码 在前端,我们可以使用 jQuery 或者其他的 JavaScript 库来发送 HTTP 请求,获取后端返回的 JSON 数据。这里我们使用 jQuery 来演示,代码如下: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>前端页面</title> </head> <body> <div id="user"></div> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script> $(function () { $.get("/users/1", function (data) { $("#user").text(JSON.stringify(data)); }); }); </script> </body> </html> ``` 在上面的代码中,我们使用了 jQuery 的 $.get 方法发送了一个 GET 请求,请求 URL 为 /users/1,表示获取 ID 为 1 的用户信息。当请求成功后,我们把返回的 JSON 数据转换为字符串,然后显示在页面上。 3. 测试 现在我们启动后端应用,然后在浏览器中打开前端页面,就可以看到 ID 为 1 的用户信息了。 这是一个简单的 Java 后端和前端对接数据的案例,希望能够帮到你。如果你有任何其他问题,请随时问我。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值