当前位置: 首页 > news >正文

uniapp中的流式输出

一、完整代码展示

  • 目前大多数的ai对话都是流式输出,也就是对话是一个字或者多个字逐一进行显示的
  • 下面是一个完整的流式显示程序,包含的用户的消息发出和ai的消息回复
<template>
  <view class="chat-container">
    <view class="messages">
      <!-- 对话气泡 -->
      <view
        v-for="(message, index) in messages"
        :key="index"
        :class="['message', message.sender]"
      >
        <text selectable="true">{{ message.text }}</text>
      </view>
      
      <!-- 加载状态 -->
      <view v-if="isLoading" class="loading-spinner"></view>
    </view>
    
    <!-- 消息输入和发送按钮 -->
    <view class="input-area">
      <textarea
        v-model="inputMessage"
        placeholder="输入消息"
        @input="adjustInputHeight"
      ></textarea>
      <button @click="sendMessage">发送</button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      messages: [],
      inputMessage: '',
      isLoading: false,
      inputHeight: 48
    };
  },
  methods: {
    sendMessage() {
      //如果输出消息为空直接返回
      if (!this.inputMessage.trim()) return;
      
      // 添加用户消息
      this.messages.push({
        text: this.inputMessage,
        sender: 'user'
      });
      
      // 初始化AI消息
      const aiIndex = this.messages.length;
      this.messages.push({
        text: '',
        sender: 'ai'
      });
      
      // 重置输入
      this.inputMessage = '';
      this.isLoading = true;

      // 发起流式请求
      const url = 'http://localhost:8081/chat';
      const params = {
        session_id: 'token',
        content: this.inputMessage
      };

      uni.request({
        url: url + '?' + this.serializeParams(params),
        method: 'GET',
        header: {
          'Accept': 'text/event-stream',
        },
        success: (res) => {
          this.processStreamResponse(res.data, aiIndex);
        },
        fail: (err) => {
          console.error('请求失败:', err);
          this.isLoading = false;
        }
      });
    },

    processStreamResponse(data, aiIndex) {
      const chunks = data.split('\n');
      let chunkIndex = 0;
      const interval = setInterval(() => {
        if (chunkIndex >= chunks.length) {
          clearInterval(interval);
          this.isLoading = false;
          return;
        }

        const chunk = chunks[chunkIndex].replace('data:', '').trim();
        if (chunk) {
          this.messages[aiIndex].text += chunk;
          this.$forceUpdate();
        }
        chunkIndex++;
      }, 50);
    },

    serializeParams(params) {
      return Object.entries(params)
        .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
        .join('&');
    },

    adjustInputHeight(e) {
      const textarea = e.target;
      textarea.style.height = 'auto';
      textarea.style.height = textarea.scrollHeight + 'px';
      this.inputHeight = textarea.scrollHeight;
    }
  }
};
</script>

<style>
.chat-container {
  height: 100vh;
  display: flex;
  flex-direction: column;
  padding: 20px;
  background-color: #f5f5f7;
}

.messages {
  flex: 1;
  overflow-y: auto;
  padding: 20px;
}

.message {
  margin: 10px 0;
  padding: 12px 16px;
  border-radius: 16px;
  max-width: 70%;
  word-wrap: break-word;
}

.message.user {
  background: linear-gradient(135deg, #cbe7ff, #cfe9ff);
  align-self: flex-end;
}

.message.ai {
  background: linear-gradient(135deg, #f0f0f0, #e0e0e0);
  align-self: flex-start;
}

.loading-spinner {
  border: 4px solid #f3f3f3;
  border-top: 4px solid #007aff;
  border-radius: 50%;
  width: 24px;
  height: 24px;
  animation: spin 1s linear infinite;
  margin: 20px auto;
}

.input-area {
  display: flex;
  gap: 10px;
  margin-top: 20px;
}

textarea {
  flex: 1;
  padding: 12px;
  border: 1px solid #ddd;
  border-radius: 8px;
  resize: none;
}

button {
  padding: 12px 24px;
  background-color: #007aff;
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
</style>

二、流式传输核心代码讲解

1、请求发起

  • 设置Accept: text/event-stream告知服务器需要流式响应
  • 通过session_id传递认证信息
  • 使用GET请求发送消息内容
uni.request({
  url: url + '?' + this.serializeParams(params),
  method: 'GET',
  header: {
    'Accept': 'text/event-stream',
  },
  success: (res) => {
    this.processStreamResponse(res.data, aiIndex);
  }
});

2、流式响应处理

  • 将响应数据按换行符分割成块
  • 使用setInterval控制显示速度(这里设置为 50ms / 块)
  • 逐块追加到 AI 消息中
  • 使用$forceUpdate强制刷新视图
processStreamResponse(data, aiIndex) {
  const chunks = data.split('\n');
  let chunkIndex = 0;
  const interval = setInterval(() => {
    if (chunkIndex >= chunks.length) {
      clearInterval(interval);
      this.isLoading = false;
      return;
    }

    const chunk = chunks[chunkIndex].replace('data:', '').trim();
    if (chunk) {
      this.messages[aiIndex].text += chunk;
      this.$forceUpdate();
    }
    chunkIndex++;
  }, 50);
}

3、加载状态管理

  • 在请求发起时显示加载状态
  • 响应处理完成后隐藏加载状态
// 发送消息时
this.isLoading = true;

// 响应处理完成
clearInterval(interval);
this.isLoading = false;

4、数据格式处理 

  • 将参数对象序列化为 URL 查询字符串
  • 使用encodeURIComponent处理特殊字符
serializeParams(params) {
  return Object.entries(params)
    .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
    .join('&');
}

5、消息显示 

  • 使用 flex 布局实现消息气泡
  • 通过selectable="true"实现文本选中
  • 根据 sender 添加不同样式
<view v-for="(message, index) in messages" :class="['message', message.sender]">
  <text selectable="true">{{ message.text }}</text>
</view>

http://www.dtcms.com/a/99321.html

相关文章:

  • 蓝桥杯 14 天 十五届蓝桥杯 数字诗意
  • 雨云云应用测评!内测持续进行中!
  • 深度学习中常见的专业术语汇总
  • SQL Server 可用性组自动种子设定失败问题
  • .NET开发基础知识1-10
  • 无人机宽带自组网机载电台技术详解,50KM超远图数传输系统实现详解
  • Python控制结构详解
  • 群体智能优化算法-流向算法(Flow Direction Algorithm, FDA,含Matlab源代码)
  • FALL靶机渗透实战:从信息收集到特权升级的完整链分析
  • postgresql 重置对应表序列最大值
  • 药用植物次生代谢的多层调控-文献精读123
  • 如何利用<ruby>、<rt>、<rp>标签实现中文注音或字符注释?
  • 车载以太网网络测试 -25【SOME/IP-报文格式-1】
  • AI助力高效办公:如何利用AI制作PPT提升工作效率
  • RAG模型
  • 医疗CMS高效管理:简化更新维护流程
  • Open HarmonyOS 5.0 分布式软总线子系统 (DSoftBus) 详细设计与运行分析报告
  • 自动化测试知识详解
  • RuoYi基础学习
  • 拦截器和过滤器详解
  • 前端D3.js面试题及参考答案
  • Linux安装Cmake (Centos 7.9)
  • Python小练习系列 Vol.3:生成有效括号组合(回溯 + DFS)
  • 【15】Selenium 爬取实战
  • stringstream的使用
  • d2025329
  • PyGame开发贪吃蛇小游戏
  • Hive SQL中 ?+.+ 的用法,字段剔除
  • 在Qt中判断输入的js脚本是否只包含函数
  • 【Linux】常见信号 + 进程和作业